问题描述
我使用的是 Dahua Facial 终端,它有一个类似(CGI 风格)的 API 和一个 SDK。我问了一些关于 dll 转换的问题,但现在我也在尝试使用 de API。
监控 Facial 处理的事件的 API 是
是 multipart/x-mixed-replace 响应如何返回带有事件数据的文本/纯文本形式的第一个边界,以及带有事件快照的图像/jpeg 的边界。
使用有关 Indy 的在线信息和 Lebeau 发表的一些有用帖子,我使用 idHttp.IoHanlder.ReadLn(IndyTextEncoding_UTF8) 读取文本数据
我尝试使用 idHttp.IOHandler.ReadByte、ReadBytes、ReadStream 读取下一个边界(图像),但没有成功。
这是使用 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�������������������������������������������������������������������������
$4�%�&'()*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ۭ
我正在创建一个类来实现 API 和 SDK,在这种情况下 (API) 来管理事件我编写了一个线程类来使用事件 API 并在事件发生时同步数据,idHttp 归线程类和它在一个 Tdispositivo 类中,所以我为包含在主类中的每个 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 = '',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 = '',Valor.ToInteger)
else if Chave = 'ErrorCode' then
ErrorCode := ifThen(Valor = '',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 = '',Valor.ToInteger)
else if Chave = 'Method' then
Metodo := ifThen(Valor = '',Valor.ToInteger)
else if Chave = 'ReaderID' then
LeitorID := ifThen(Valor = '',Valor.ToInteger)
else if Chave = 'Similarity' then
Similaridade := ifthen(Valor = '',Valor.ToInteger)
else if Chave = 'SnapPath' then
snappath := Valor
else if Chave = 'Status' then
Status := ifthen(Valor = '',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 = '',Valor.ToInteger)
else if Chave = 'UserType' then
begin
i := 34;
Concluido := True;
UserType := ifthen(Valor = '',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;
第一个问题,我可以吗?印地能做到吗?有什么建议吗?
第二个问题,我观察到在 GET 运行时检索数据的循环,当我在数据结束后尝试 readLn 时优雅地引发关闭。在 Firefox 中,GET 仍然在图像之后运行并且仍在工作,在我的情况下,GET 已完成并且 threand.execute 再次调用 GET。
是否可以保持 GET 运行以避免过早中断并在出现时处理新数据?
EDIT:当这部分代码运行时,行 NewDecoder := TIdMessageDecoderMIME(Decoder).ReadBody(BodyStream,MsgEnd);
优雅地结束,深入查看 Indy 代码我发现了 TIOHandler.ReadLn
如何执行 {{1 }} 当 CheckFordisconnect(True,True)
时,这会中断执行而不检索数据的最后一个字节。
考虑到这一点,我怀疑与 LTerm = -1
的关系始终为 Responde.KeepAlive
,而 False
的内容始终为 Connection
这解释了为什么 Decoder.Readybody引发 close
并中断我的执行。
但是,如果我在例如上运行 api 调用。 Firefox,FF 获取图像后连接仍在运行,仅在超时时中断。
终端发送的标头上可能有什么东西吗?或者idHttp解析headers数据的方式?
Close gracefully
编辑:使用 New TIdHTTP hoNoReadMultipartMIME flag 示例,我编写了一个测试应用程序来尝试从边界获取图像
//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;
当调用 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
会运行 readbody 函数,读取字节直到它在达到 'Content-Length' 大小之前崩溃。
我试图在它崩溃时进行隔离,但是可以肯定的是,当它尝试读取下一个数据时会发生错误。但我还没有在 indy 源上找到异常的触发器
编辑:得到一个图像,但无法继续阅读,直到服务器关闭连接导致响应已经关闭。
工作代码
NewDecoder := Decoder.ReadBody(BodyStream,MsgEnd);
解决方法
您可以使用 TIdHTTP
完成您的要求,但这需要一些额外的工作。 Indy 网站上的以下博客文章提供了详细信息:
https://www.indyproject.org/2014/03/05/new-tidhttp-honoreadmultipartmime-flag/
简而言之,您需要在 hoNoReadMultipartMIME
属性中启用 TIdHTTP.HTTPOptions
标志,以便 TIdHTTP.Get()
不会尝试从 TIdHTTP.IOHandler
收到 HTTP 标头后。这将允许您自己读取 MIME 数据。您可以使用 Indy 的 TIdMessageDecoderMIME
类来帮助阅读。博客文章中提供了一个代码示例。