Delphi Indy TIdHttp 和 multipart/x-mixed-replace with Text 和 jpeg 图像

问题描述

我使用的是 Dahua Facial 终端,它有一个类似(CGI 风格)的 API 和一个 SDK。我问了一些关于 dll 转换的问题,但现在我也在尝试使用 de API。

监控 Facial 处理的事件的 API 是

http://192.168.1.201/cgi-bin/snapManager.cgi?action=attachFileProc&Flags[0]=Event&Events=[AccessControl]

是 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 类来帮助阅读。博客文章中提供了一个代码示例。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...