8 messaggi dal 27 gennaio 2011
Ciao a Tutti e buona domenica,

Mio Ambiente di lavoro: .NET 3.5, IIS 5.1, SQLServer 2008 , VisualStudio 2008, Linguaggio C#.

Ho scritto un httphandler che deve downloadare files anche molto grandi ( es: 180 MB ) da sito remoto.
Il punto è che la chiamata remota (webquery ) viene fatta attraverso un radware che si trova in mezzo , e questo radware ha un timeout molto ristretto ( 1 minuto ). In altre parole se non c'è attività sul canale entro 1 min., cade tutto.

Sto usando normali HttpWebRequest e HttpWebResponse, con files piccoli, ad esem. 5 MB dove il dowload viene terminato entro il minuto, funziona tutto naturalmente.

Inoltre se faccio girare l'handler direttamente in rete senza passare dal radware, anche quello da 180 MB lo scarico senza problemi in circa 12 min.

Ho fatto ricerche in Internet , ma non ho trovato granchè, probabilmente non ho impostato la ricerca giusta.

Allego il pezzo di codice dove cade la comunicazione, premetto che non sono un esperto di Web, quindi non meravigliatevi se vedete strafalcioni, anzi vi prego di dirmelo.

private Stream GetFileFromCjd(HttpContext context,
string url,
out string contentType,
bool canRetry,
out string nomeFile )
{
HttpWebRequest httpRequest = null;

Stream outputStream = new MemoryStream();

contentType = "plain/text";

FGAwWS refFGAwWS = new FGAwWS();
refFGAwWS.Url = ConfigurationManager.AppSettings["FGAwWS"];
refFGAwWS.Discover();

byte[] data = null;

LoggerSoftwareUtility.Debug("Call GetFileFromCjd from [getFile.ashx]" +
" [UTC] " + DateTime.Now.ToString());

nomeFile = url.Substring(url.LastIndexOf("/") + 1);

//Check for stored cookie in database.
string ServerURL = ConfigurationManager.AppSettings["CJDServerURL"];
DCCookieMonster Cookies = CJDDialerCONNECTClient.loginToDealerConnect(ServerURL);

Uri uri = new Uri(url);
httpRequest = (HttpWebRequest)WebRequest.Create(uri);

//Force use of workaround for "The underlying connection was closed" bug.
httpRequest.KeepAlive = false;
System.Net.ServicePointManager.Expect100Continue = false;

// allows for validation of SSL conversations
ServicePointManager.ServerCertificateValidationCallback +=
new RemoteCertificateValidationCallback(CJDDialerCONNECTClient.ValidateRemoteCertificate);

CJDDialerCONNECTClient.SetProxyInformations(ref httpRequest); // Using PROXY IBM-FIAT

httpRequest.AllowAutoRedirect = false;
httpRequest.CachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.NoCacheNoStore);
httpRequest.Method = "POST";

httpRequest.CookieContainer = new CookieContainer();
StringBuilder CookieStringBld = Cookies.toStringBuffer();
string CookieString = CookieStringBld.ToString();
httpRequest.CookieContainer.SetCookies(uri, CookieString);
//
//LoggerSoftwareUtility.Debug("GetFileFromCjd: [ httpRequest.GetRequestStream ] START. " +
// " Actual Time:" + DateTime.Now.ToString());
////
//Stream sendStream = httpRequest.GetRequestStream();
//long len = sendStream.Length;

//StreamWriter strmWrtr = new StreamWriter(sendStream);
//strmWrtr.Flush();
//strmWrtr.Close();

//LoggerSoftwareUtility.Debug("GetFileFromCjd: [ httpRequest.GetRequestStream ] finished. START RESPONSE " +
// " Initial hour:" + DateTime.Now.ToString());
try
{
LoggerSoftwareUtility.Debug("GetFileFromCjd: [ httpRequest.GetResponse ] START RESPONSE " +
" Initial Time:" + DateTime.Now.ToString());

using (HttpWebResponse Response = (HttpWebResponse)httpRequest.GetResponse())
{
// Check Status.code before Read data
if (Response.StatusCode == HttpStatusCode.OK)
{
long cl = Response.ContentLength;

//old way
//outputStream = new MemoryStream();

if (url.ToLower().EndsWith(".efd"))
contentType = "application/x-compressed"; //Response.ContentType;
else
contentType = Response.ContentType;

context.Response.ContentType = contentType;
context.Response.AddHeader("Content-Length", cl.ToString());
context.Response.AddHeader("Content-Disposition", string.Format("attachment; filename={0}", nomeFile));

Stream RespStrm = Response.GetResponseStream();
data = new byte[BUFFLENGTH]; // actual dimension 8K
int count = 0;

LoggerSoftwareUtility.Debug("GetFileFromCjd: START LOOP on [ Response.GetResponseStream ] " +
"FILENAME: " + nomeFile + " Dimension: " + cl.ToString() +
" Start Time: " + DateTime.Now.ToString() );
do
{
//count = RespStrm.Read(data, 0, BUFFLENGTH); // Original
count = RespStrm.Read(data, 0, data.Length);
//flush and finalize write to output
context.Response.OutputStream.Write(data, 0, count);
context.Response.OutputStream.Flush();
}
while (count != 0);

//outputStream.Position = 0;
RespStrm.Close();
context.Response.OutputStream.Close();

LoggerSoftwareUtility.Debug("GetFileFromCjd: END DOWNLOAD on [ Response.GetResponseStream ] " +
"FILENAME: " + nomeFile + " Dimension: " + cl.ToString() +
" End Time: " + DateTime.Now.ToString());
return null;

}
else if (Response.StatusCode == HttpStatusCode.Found)
{
//Maybe credentials are not uptodate and must be refreshed.
new DataManager.FGACJDHelper().ResetAuthCookie();

LoggerSoftwareUtility.Error("GetFileFromCjd: Response.StatusCode == HttpStatusCode.Found " +
" Flag [canRetry] == " + canRetry.ToString() +
" Current Time: " + DateTime.Now.ToString() );
//And retry to contact service.
if (canRetry)
{
try
{
Response.Close();
}
catch (Exception) { /*Nothing to do. Yeah. Just recall service, once again*/ }

outputStream = GetFileFromCjd(context, url, out contentType, false, out nomeFile);
}
else
{
LoggerSoftwareUtility.Error("GetFileFromCjd: Response.StatusCode == HttpStatusCode.Found " +
" Flag [canRetry] == " + canRetry.ToString() +
" Already Retried: Unable to contact CJD Server " +
" Current Time: " + DateTime.Now.ToString());

throw new Exception("Unable to contact CJD Server");
}
}
}
}
catch (Exception ex)
{
string str = ex.Message;
outputStream = new MemoryStream(UTF8Encoding.UTF8.GetBytes(str));

LoggerSoftwareUtility.Error("GetFileFromCjd: " + outputStream +
" Actual hour: " + DateTime.Now.ToString());
}

return outputStream;
}

==============================================================

Come potete vedere dal codice, il nocciolo del problema è il do While, che dura troppo, anche se comunque arriva in fondo.

Qualcuno mi può aiutare ??

Vi ringrazio.

Giando59.
11.886 messaggi dal 09 febbraio 2002
Contributi
Dunque... puoi spiegare perché la tua applicazione deve fare da proxy per il download di questi file? Forse così si riesce a darti un consiglio più mirato.

Vedo che lato server setti un cookie per la request, lo fai perché il client non può/deve venire a conoscenza delle credenziali usate per scaricare il file dall'altro server?

Permettimi subito una digressione: tu stai scrivendo un proxy a livello applicativo, forse la soluzione migliore sarebbe invece realizzarlo ad un livello più basso del modello OSI? Dovresti chiedere ad un amministratore di sistema, io non ho molte competenze in merito quindi potrei aver già detto delle stupidaggini :]

Inoltre guarda questo articolo di Daniele.
http://blogs.aspitalia.com/daniele/post2315/Usare-IIS-Reverse-Proxy.aspx
Può darsi che l'Application Requuest Routing Module ti sia di qualche utilità (?)


Invece, guardando il codice, ci sono diverse cose da considerare, la più importante delle quali è la poca scalabilità che una soluzione come quella offre.
Se anche non ci fossero problemi di timeout, cosa succederebbe se tanti utenti ti scaricano contemporaneamente un file da 180 mega?
Beh, vai ad esaurire velocemente le risorse del sistema:

- la RAM perché la pagina tiene il file in memoria fintanto che è in download (stai leggendo lo stream della response in un array di byte che poi ri-scrivi nella response);
- la banda, perché tutte le request andranno a scaricare i file contemporaneamente;
- il thread pool di IIS, perché il server non ti consente di tenere tante request attive in un dato momento. In realtà questo limite è abbastanza ampio, però idealmente se qualcuno decidesse di bloccarti l'applicazione potrebbe riuscirci benissimo facendo tante chiamate a quella pagina che va in esecuzione per minuti e minuti.

A questo punto ti do 2 consigli:

SE HAI ACCESSO AL SERVER CHE POSSIEDE I FILE
Fai in modo che sia direttamente quel server a fornire i file all'utente mediante una semplice richiesta GET. La tua applicazione dovrà solo fare una redirect alla pagina per il download. Se pensi che così i file siano troppo esposti e non vuoi che l'utente possa scaricare indiscriminatamente quello che vuole, allora la tua applicazione, subito prima di effettuare la ridirezione, dovrebbe informare l'altro server che "c'è un utente xyz con IP aaa.bbb.ccc.ddd che vorrebbe scaricare il file 123.txt, autorizzalo per tot minuti".
Così l'onere di servire il file non sarebbe più della tua applicazione.

SE NON HAI ACCESSO, O SE LA PERSONA CHE CE L'HA NON COLLABORA
Devi necessariamente inserire le richieste in una coda e svolgerne 1-2 alla volta. La tua applicazione, anziché andare a scaricare il file, mette invece un messaggio in una coda. Puoi usare la classe ConcurrentQueue a tale scopo.
http://msdn.microsoft.com/it-it/library/dd267265.aspx

All'utente farai immediatamente vedere questo messaggio: "Abbiamo ricevuto la tua richiesta di download, ti preghiamo di attendere che la procedura sia completa".
A questo punto, lato server ci sarà un BackgroundWorker che avrai istanziato nell'Application_Start del global.asax e che toglierà il primo elemento dalla coda e comincerà a scaricare il file.

Qui c'è un esempio di come usare il BackgroundWorker in un'applicazione web. E' un modo di far girare qualcosa fuori dalla request, così che l'operazione non sia soggetta a timeout.
http://www.dotnetfunda.com/articles/article613-background-processes-in-asp-net-web-applications-.aspx

Periodicamente esso farà rapporto sulla percentuale di completamento del download.
Aggiorna la pagina dell'utente ogni 10 secondi per fargli vedere a che punto è il suo download. Quando è completato, gli mostri il link al file che ormai si troverà sul tuo server.

Se vuoi che il file sia alla mercé di tutti, allora magari salvalo dentro una cartella che non sia direttamente accessibile da web, come /App_Data e poi glielo servi mediante un HttpHandler. Vedi qui l'esempio. Dato che hai file grandi è importante usare Response.TransmitFile.
http://www.aspitalia.com/script/944/Inviare-File-Grandi-Dimensioni-HttpHandler-ASP.NET.aspx

ciao
Modificato da BrightSoul il 30 maggio 2011 21.23 -

Enjoy learning and just keep making
8 messaggi dal 27 gennaio 2011
Ciao e Grazie BrightSoul per i suggerimenti,

" Dunque... puoi spiegare perché la tua applicazione deve fare da proxy per il download di questi file? Forse così si riesce a darti un consiglio più mirato. "

Francamente non ne sono a conoscenza, mi sono trovato con lo scenario descritto precedentemente, e con il timeout da 1 min che troncava il canale.

" Vedo che lato server setti un cookie per la request, lo fai perché il client non può/deve venire a conoscenza delle credenziali usate per scaricare il file dall'altro server? "
Non può conoscere le credenziali.


Le Tue annotazioni sul fatto che se c'è un grande traffico sul dowload creano problemi di RAM e di banda sono sicuramente centrate,

Ho nel frattempo trovato una soluzione non definitiva, forzando la Response a non bufferizzare. ti allego il pezzetto di codice:

// Force to write immediatly on targhet the bytes read.
context.Response.BufferOutput = false;

int NumBytesToWrite = 0;
do
{
if (BytesToRead >= BUFFLENGTH)
{
data = RespStrm.ReadBytes(BUFFLENGTH);
NumBytesToWrite = BUFFLENGTH;
}
else
{
data = RespStrm.ReadBytes(BytesToRead);
NumBytesToWrite = BytesToRead;
}
context.Response.OutputStream.Write( data, 0, NumBytesToWrite );
context.Response.OutputStream.Flush();

if (BytesToRead >= BUFFLENGTH)
{
BytesToRead -= BUFFLENGTH;
BytesRead += BUFFLENGTH;
}
else
{
BytesRead += BytesToRead;
BytesToRead = 0;
}
}
while (BytesToRead != 0);


Con "context.Response.BufferOutput = false;" ho ottenuto quello che cercavo, parte subito la dialog del file downloading, e non perdo la connesione sul canale.

Molto interessanti i due suggerimenti "BackgroundWorker", e "Response.Transmitfile". Li prenderò sicuramente in considerazione.

Grazie Mille ancora per l'aiuto e Buona Giornata.

Giando59

Torna al forum | Feed RSS

ASPItalia.com non è responsabile per il contenuto dei messaggi presenti su questo servizio, non avendo nessun controllo sui messaggi postati nei propri forum, che rappresentano l'espressione del pensiero degli autori.