2014-12-29 90 views
-1

如何使用Indy HTTP Server实现以下功能。客户端访问http:// server_name:端口,服务器向其返回视频流,该视频流存储在http:// server_name_video:port/video1.mpgDelphi视频流Http服务器

+0

你为什么不使用Tembeddedwb,而不是印来实现这一目的 – DelphiStudent

+0

TEmbeddedWB是一个客户端浏览器。 OP想要创建一个服务器。 –

回答

6

TIdHTTPServer本身不支持流媒体。你必须手动实现它。在您的OnCommandGet事件处理程序,根据需要指定您所需的值给AResponseInfo参数,如ContentTypeTransferEncoding,并留下ContentTextContentStream性质未分配,然后调用AResponseInfo.WriteHeader()只是响应头发送到客户端,然后进入一个循环书写您的视频媒体数据块(根据RFC 2616 Section 3.6.1 Chunked Transfer Coding 中描述的格式),直到客户端断开连接或达到媒体结束。例如:

procedure TForm1.IdHTTPServer1CommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); 
var 
    FS: TFileStream; 
    Buf: TIdBytes; 
    BufLen: Integer; 
begin 
    if ARequestInfo.Document <> '/' then 
    begin 
    AResponseInfo.ResponseNo := 404; 
    Exit; 
    end; 
    FS := TFileStream.Create('video1.mpg', fmOpenRead or fmShareDenyWrite); 
    try 
    AResponseInfo.ResponseNo := 200; 
    AResponseInfo.ContentType := 'video/mpeg'; 
    AResponseInfo.TransferEncoding := 'chunked'; 
    AResponseInfo.WriteHeader; 

    SetLength(Buf, 1024); 
    repeat 
     BufLen := FS.Read(Buf[0], 1024); 
     if BufLen < 1 then Break; 
     AContext.Connection.IOHandler.WriteLn(IntToHex(BufLen, 1)); 
     AContext.Connection.IOHandler.Write(Buf, BufLen); 
     AContext.Connection.IOHandler.WriteLn; 
    until False; 

    AContext.Connection.IOHandler.WriteLn('0'); 
    AContext.Connection.IOHandler.WriteLn; 
    finally 
    FS.Free; 
    end; 
end; 

另一方面,如果您尝试从其他服务器流式传输媒体,则会变得更加复杂。您必须向其他服务器发送请求,接收响应,然后将数据转发给客户端。但TIdHTTP不支持流式媒体,因此难以将其用于此目的。你可能最终不得不使用TIdTCPClient,而不是直接和实现HTTP协议自己,例如需要部分:

procedure TForm1.IdHTTPServer1CommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); 
var 
    Client: TIdTCPClient; 
    Headers: TIdHeaderList; 
    S, ResponseCode, ResponseText: string; 
    Size: Int64; 
    Strm: TIdTCPStream;  
begin 
    if ARequestInfo.Document <> '/' then 
    begin 
    AResponseInfo.ResponseNo := 404; 
    Exit; 
    end; 
    Client := TIdTCPClient.Create; 
    try 
    Client.Host := 'server_name_video'; 
    Client.Port := port; 
    Client.Connect; 
    try 
     Client.IOHandler.WriteLn('GET /video1.mpg HTTP/1.0'); 
     Client.IOHandler.WriteLn('Host: server_name_video'); 
     Client.IOHandler.WriteLn; 

     ResponseText := Client.IOHandler.ReadLn; 
     Fetch(ResponseText); 
     ResponseText := TrimLeft(ResponseText); 
     ResponseCode := Fetch(ResponseText, ' ', False); 
     ResponseCode := Fetch(ResponseCode, '.', False); 

     if ResponseCode <> '200' then 
     begin 
     AResponseInfo.ResponseNo := StrToInt(ResponseCode); 
     AResponseInfo.ResponseText := ResponseText; 
     Exit; 
     end; 

     Headers := TIdHeaderList.Create(QuoteHTTP); 
     try 
     Headers.FoldLength := MaxInt; 

     repeat 
      s := Client.IOHandler.ReadLn; 
      if s = '' then Break; 
      Headers.Add(s); 
     until False; 

     Strm := TIdTCPStream.Create(AContext.Connection); 
     try 
      AResponseInfo.ResponseNo := 200; 
      AResponseInfo.ContentType := Headers.Values['Content-Type']; 

      if Pos('chunked', Headers.Values['Transfer-Encoding']) <> 0 then 
      begin 
      AResponse.TransferEncoding := 'chunked'; 
      AResponseInfo.WriteHeader; 

      repeat 
       s := Client.IOHandler.ReadLn; 
       AContext.Connection.IOHandler.WriteLn(s); 
       Size := StrToInt64('$'+Fetch(s, ';')); 
       if Size = 0 then Break; 
       Client.IOHandler.ReadStream(Strm, Size, False); 
       s := Client.IOHandler.ReadLn; 
       AContext.Connection.IOHandler.WriteLn(s); 
      until false; 

      repeat 
       s := Client.IOHandler.ReadLn; 
       AContext.Connection.IOHandler.WriteLn(s); 
      until s = ''; 
      end 
      else if Headers.IndexOfName('Content-Length') <> -1 then 
      begin 
      Size := StrToInt64(Headers.Values['Content-Length']); 
      AResponseInfo.ContentLength := Size; 
      AResponseInfo.WriteHeader; 
      if Size > 0 then 
       Client.IOHandler.ReadStream(Strm, Size, False); 
      end else 
      begin 
      AResponseInfo.CloseConnection := true; 
      AResponseInfo.WriteHeader; 
      try 
       Client.IOHandler.ReadStream(Strm, -1, True); 
      except 
       on E: EIdSocketError do begin 
       if not (E.LastError in [10053, 10054, 10058]) then 
        raise; 
       end; 
      end; 
      end; 
     finally 
      Strm.Free; 
     end; 
     finally 
     Headers.Free; 
     end; 
    finally 
     Client.Disconnect; 
    end; 
    finally 
    Client.Free; 
    end; 
end; 

当然,如果需要的话,你必须还实现了的东西,如HTTP认证的请求字节范围等

更新:或者,而不是使用TIdTCPClient直接,你可以使用毕竟TIdHTTP,只要给它,因为它被写入到写回原来的客户端的输出TStream。你可以使用TIdEventStream用于该目的,或者自己写TStream类,如:

type 
    TMyStream = class(TIdBaseStream) 
    protected 
    FHTTP: TIdHTTP; 
    FClient: TIdIOHandler; 
    FResponse: TIdHTTPResponseInfo; 
    function IdRead(var VBuffer: TIdBytes; AOffset, ACount: Longint): Longint; override; 
    function IdWrite(const ABuffer: TIdBytes; AOffset, ACount: Longint): Longint; override; 
    function IdSeek(const AOffset: Int64; AOrigin: TSeekOrigin): Int64; override; 
    procedure IdSetSize(ASize: Int64); override; 
    public 
    constructor Create(AHTTP: TIdHTTP; AClient: TIdIOHandler; AResponse: TIdHTTPResponseInfo); 
    destructor Destroy; override; 
    end; 

constructor TMyStream.Create(AHTTP: TIdHTTP; AClient: TIdIOHandler; AResponse: TIdHTTPResponseInfo); 
begin 
    inherited Create; 
    FHTTP := AHTTP; 
    FClient := AClient; 
    FResponse := AResponse; 
end; 

destructor TMyStream.Destroy; 
begin 
    if FResponse.HeaderHasBeenWritten then 
    begin 
    FClient.WriteLn('0'); 
    FClient.WriteLn(''); 
    end; 
end; 

function TMyStream.IdRead(var VBuffer: TIdBytes; AOffset, ACount: Longint): Longint; 
begin 
    Result := 0; 
end; 

function TMyStream.IdWrite(const ABuffer: TIdBytes; AOffset, ACount: Longint): Longint; 
begin 
    if not FResponse.HeaderHasBeenWritten then 
    begin 
    FResponse.ResponseNo := 200; 
    FResponseInfo.ContentType := FHTTP.Response.ContentType; 
    FResponse.TransferEncoding := 'chunked'; 
    FResponse.WriteHeader; 
    end; 
    FClient.WriteLn(IntToHex(IndyLength(ABuffer, ACount, AOffset))); 
    FClient.Write(ABuffer, ACount, AOffset); 
    FClient.WriteLn; 
end; 

function TMyStream.IdSeek(const AOffset: Int64; AOrigin: TSeekOrigin): Int64; 
begin 
    Result := 0; 
end; 

procedure TMyStream.IdSetSize(ASize: Int64); 
begin 
end; 

procedure TForm1.IdHTTPServer1CommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); 
var 
    HTTP: TIdHTTP; 
    Strm: TMyStream; 
begin 
    if ARequestInfo.Document <> '/' then 
    begin 
    AResponseInfo.ResponseNo := 404; 
    Exit; 
    end; 
    HTTP := TIdHTTP.Create; 
    try 
    HTTP.HTTPOptions := HTTP.HTTPOptions + [hoNoProtocolErrorException]; 
    Strm := TMyStream.Create(HTTP, AContext.Connection.IOHandler, AResponseInfo); 
    try 
     HTTP.Get('http://server_name_video:'+IntToStr(port)+'/video1.mpg', Strm); 
    finally 
     Strm.Free; 
    end; 
    if not AResponseInfo.HeaderHasBeenWritten then 
    begin 
     AResponseInfo.ResponseNo := HTTP.ResponseCode; 
     AResponseInfo.ResponseText := HTTP.ResponseText; 
    end; 
    finally 
    HTTP.Free; 
    end; 
end; 

另外,如果其他服务器支持chunked响应,您可以:

  1. 使用新TIdHTTP.OnChunkReceived事件将每个接收到的块写入客户端,类似于以上,而不使用自定义TStream(您仍然必须提供TStreamTIdHTTP.Get()。您可以使用TIdEventStream,并且不要将任何事件处理程序分配给它,因此数据被丢弃。这可能会在未来发生变化)。

  2. 使TIdHTTP的新hoNoReadChunked标志,以及从直接TIdHTTP.IOHandler到客户端然后只是隧道的原始数据,例如通过使用一个与TIdTCPStreamAContext.Connection.IOHandler.WriteStream()

New TIdHTTP flags and OnChunkReceived event

+0

我不禁赞赏你的承诺,雷米! –

+0

Remy,当我尝试下载文件Headers.Values ['Content-Type'] ='application/octet-stream'。并在线Client.IOHandler.ReadStream(Strm,-1,True);引发异常10053.你能帮我解决这个错误吗? –

+0

第二个示例充当代理来转发来自其他服务器的数据。如果Content-Type是application/octet-stream,那么这就是其他服务器实际报告的内容。至于10053错误,这是正常的行为。 'AReadUntilDisconnect'参数设置为True,并且10053是'WSAECONNABORTED',它是断开连接期间几个可能的错误代码之一。 'ReadStream()'当前不处理10053作为断开连接(这是一个TODO项目),所以你将不得不手动处理它。我更新了我的示例以表明这一点。 –