2014-03-06 5 views
5

TL; DR: Wie kann ich große Dateien mit einer bekannten Größe mit WCF streamen, und weiterhin Fortschritt (Content-Length) an den Endbenutzer (ein Webbrowser)?Übertragung von großen Dateien: Kombination von Streaming-Übertragung und Content-Länge

Ich habe einen WCF REST Service, der sehr große Dateien (1-20Gb) herunterlädt und dann an einen Webbrowser liefert. Um zu vereinfachen, denke an meinen Dienst als Stellvertreter. Dies zwingt mich, TransferMode = Streamed oder TransferMode = StreamedResponse auf der Bindung, oder der End-Client muss warten, bis die Quelldateien auf den Webserver heruntergeladen werden, bevor der eigentliche Download beginnt. Außerdem wird durch den gepufferten Übertragungsmodus der Server für große Dateien (RAM-Auslastung) beendet. Zwischenplattenspeicher ist keine Option. Von TransferMode Manpage:

(...) Buffered Transfers halten Sie die gesamte Nachricht in einem Speicherpuffer bis die Übertragung abgeschlossen ist.

Aber wenn TransferMode zu Streamed oder StreamedResponse Einstellung WCF nicht mehr gibt den Header Content-length an den Client, und ein neuer Header Transfer-Encoding: chunked hinzugefügt wird. Dies steht im Einklang mit wikipedias article auf segmentierte Übertragungs:

(...) verwendet den Transfer-Encoding HTTP-Header anstelle des Content-Length-Header (...)

Aber Ich weiß immer die Größe der Daten, die vorher übertragen werden müssen, und für den Endbenutzer ist es sehr frustrierend, die Größe des Downloads nicht zu kennen. Also:

(Wie) kann ich die WCF-Bindung zu verwenden, um einen „Streaming“ Transfer-Modus konfigurieren (genauer gesagt nicht die gesamte Nachricht Pufferung vor dem Senden) und immer noch die Content-Length Header verwenden?

Einige Leitungen:

  • This q/a besagen, dass der HTTP-Standard sowohl Transfer-Encoding und Content-length: 123456 in der gleichen Nachricht nicht zulässt, so dass ich denke, dass nicht eine Option?

  • Ich habe versucht, die Header modifizieren einen Inspektor in IDispatchMessage.BeforeSendReply aber an dieser Stelle die Content-Length Header entfernt wurde noch nicht verwendet wird, und Transfer-Encoding noch nicht gesetzt worden ist, so ist es „zu früh“. Ich habe später gelesen, dass Chunked-Transfer-Codierung auf TCP-Ebene ist, so dass das Ändern des Headers zu diesem Zeitpunkt wahrscheinlich nicht funktionieren wird, selbst wenn ich könnte.

  • Ich habe versucht Einstellung aspNetCompatibilityEnabled="true", Einstellung von wcf Übertragungsmodus auf gepufferte Ausgabe (Standard) und dann System.Web.HttpContext.Current.Response.BufferOutput = false;. Dies wird jedoch von der wcf ignoriert, die Nachricht ist eindeutig gepuffert.

  • Es scheint eine fehlende Funktion zu sein, nach this link. Aber es kann immer noch eine schrullige Workaround irgendwo sein ..

Antwort

2

Dies ist eine getestete Abhilfe ist, dass nur für WCF unter IIS funktioniert - ich habe keine Lösung nicht für eine Selbst gefunden gehosteter Dienst

Kurz gesagt - schalten Sie aspNetCompatibility ein, was Ihnen einen Laufzeitzugriff auf System.Web.HttpContext.Current ermöglicht.

Web.config:

(...) 
    <system.serviceModel> 
     <bindings> 
      <webHttpBinding> 
       <binding transferMode="Streamed"> 
       </binding> 
      </webHttpBinding> 
     </bindings> 
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" /> 
(...) 
</system.serviceModel> 

und in Ihrer Service-Funktion, die einen Stream zurück:

HttpContext.Current.Response.Headers.Add("Content-Length", 
contentLength.ToString()); 

so etwas wie die folgenden werden stillschweigend ignoriert:

WebOperationContext.Current.OutgoingResponse.Headers["Content-Length"] = 
     contentLength.ToString(); 

Einfach so! Creds geht zu Uffe Lausen's question on Msdn

0

Wenn Sie die Kontrolle über den Client-Code haben, können Sie die Datenlänge als die ersten Bytes in Antwortstream schreiben könnte, ist dies, wie ich es tun, große Dateien zu übertragen, aber der Kunde, dass der erste wissen müssen Bytes sind nur die Größe und enthalten keine Daten.

Beispiel @server:

public Stream GetBigData() 
{ 
    byte[] bigData = GetBigDataFromSomeWhere(); 
    var ms = new MemoryStream(); 
    var lengthInfo = BitConverter.GetBytes(bigData.Length); 
    ms.Write(lengthInfo, 0, lengthInfo.Length); 
    ms.Write(bigData , 0, bigData .Length); 
    ms.Flush(); 
    ms.Position = 0; 
    return ms; 
} 

Beispiel @client:

using (var respStream = yourService.GetBigData()) 
using (var resultStream = new MemoryStream()) 
{ 
    //read first 4 bytes 
    var lengthBuffer = new byte[4]; 
    respStream.Read(lengthBuffer, 0, 4); 
    var totalCount = BitConverter.ToInt32(lengthBuffer, 0); 
    resultStream.Capacity = totalCount; 

    //read rest 
    var readcount = 0; 
    var buffer = new byte[1024 * 32]; 
    while ((readcount = respStream.Read(buffer, 0, buffer.Length)) > 0) 
    { 
     resultStream.Write(buffer, 0, readcount); 
     UpdateProgress(readcount, totalCount) 
    } 
} 
+0

leider, wie in der Frage angegeben (ich betone das etwas, so dass es klarer ist), ist der Client ein Webbrowser. Jede Umgehung auf der Client-Seite müsste daher JavaScript oder einen anderen browser-verfügbaren Trick verwenden. Obwohl, wenn jemand "Nein" auf meine Frage antwortet, muss ich möglicherweise einen benutzerdefinierten Downloader betrachten, der wahrscheinlich so aussieht wie Ihre Antwort. – Tewr