2011-12-11 16 views
11

Estoy tratando de recuperar un archivo subido desde mi servidor web de archivos. A medida que el cliente envía sus archivos a través de un formulario web (archivos aleatorios), debo analizar la solicitud para extraer el archivo y procesarlo más adelante. Básicamente, el código sale como:HttpListener y carga

HttpListenerContext context = listener.GetContext(); 
HttpListenerRequest request = context.Request; 
StreamReader r = new StreamReader(request.InputStream, System.Text.Encoding.Default); 
// this is the retrieved file from streamreader 
string file = null; 

while ((line = r.ReadLine()) != null){ 
    // i read the stream till i retrieve the filename 
    // get the file data out and break the loop 
} 
// A byststream is created by converting the string, 
Byte[] bytes = request.ContentEncoding.GetBytes(file); 
MemoryStream mstream = new MemoryStream(bytes); 

// do the rest 

Como resultado, soy capaz de recuperar archivos de texto, pero para todos los demás archivos, que están dañados. ¿Podría alguien decirme cómo analizar correctamente estas HttplistnerRequests (o proporcionar una alternativa ligera)?

+0

Debo entender que está utilizando enctype = "multipart/form-data" en su formulario web? Si es así, parece que estás simplificando la forma se lee el contenido. –

Respuesta

13

Creo que están haciendo las cosas más difícil para ti de lo necesario al hacer esto con una HttpListener en lugar de usar las instalaciones integradas de ASP.Net. Pero si debe hacerlo de esta manera, aquí hay un código de muestra. Nota: 1) Supongo que está usando enctype="multipart/form-data" en su <form>. 2) Este código está diseñado para ser utilizado con un formulario que contiene sólo su <input type="file" /> si quieres publicar otros campos o varios archivos que tendrá que cambiar el código. 3) Esto pretende ser una prueba de concepto/ejemplo, puede tener errores y no es particularmente flexible.

static void Main(string[] args) 
{ 
    HttpListener listener = new HttpListener(); 
    listener.Prefixes.Add("http://localhost:8080/ListenerTest/"); 
    listener.Start(); 

    HttpListenerContext context = listener.GetContext(); 

    SaveFile(context.Request.ContentEncoding, GetBoundary(context.Request.ContentType), context.Request.InputStream); 

    context.Response.StatusCode = 200; 
    context.Response.ContentType = "text/html"; 
    using (StreamWriter writer = new StreamWriter(context.Response.OutputStream, Encoding.UTF8)) 
     writer.WriteLine("File Uploaded"); 

    context.Response.Close(); 

    listener.Stop(); 

} 

private static String GetBoundary(String ctype) 
{ 
    return "--" + ctype.Split(';')[1].Split('=')[1]; 
} 

private static void SaveFile(Encoding enc, String boundary, Stream input) 
{ 
    Byte[] boundaryBytes = enc.GetBytes(boundary); 
    Int32 boundaryLen = boundaryBytes.Length; 

    using (FileStream output = new FileStream("data", FileMode.Create, FileAccess.Write)) 
    { 
     Byte[] buffer = new Byte[1024]; 
     Int32 len = input.Read(buffer, 0, 1024); 
     Int32 startPos = -1; 

     // Find start boundary 
     while (true) 
     { 
      if (len == 0) 
      { 
       throw new Exception("Start Boundaray Not Found"); 
      } 

      startPos = IndexOf(buffer, len, boundaryBytes); 
      if (startPos >= 0) 
      { 
       break; 
      } 
      else 
      { 
       Array.Copy(buffer, len - boundaryLen, buffer, 0, boundaryLen); 
       len = input.Read(buffer, boundaryLen, 1024 - boundaryLen); 
      } 
     } 

     // Skip four lines (Boundary, Content-Disposition, Content-Type, and a blank) 
     for (Int32 i = 0; i < 4; i++) 
     { 
      while (true) 
      { 
       if (len == 0) 
       { 
        throw new Exception("Preamble not Found."); 
       } 

       startPos = Array.IndexOf(buffer, enc.GetBytes("\n")[0], startPos); 
       if (startPos >= 0) 
       { 
        startPos++; 
        break; 
       } 
       else 
       { 
        len = input.Read(buffer, 0, 1024); 
       } 
      } 
     } 

     Array.Copy(buffer, startPos, buffer, 0, len - startPos); 
     len = len - startPos; 

     while (true) 
     { 
      Int32 endPos = IndexOf(buffer, len, boundaryBytes); 
      if (endPos >= 0) 
      { 
       if (endPos > 0) output.Write(buffer, 0, endPos-2); 
       break; 
      } 
      else if (len <= boundaryLen) 
      { 
       throw new Exception("End Boundaray Not Found"); 
      } 
      else 
      { 
       output.Write(buffer, 0, len - boundaryLen); 
       Array.Copy(buffer, len - boundaryLen, buffer, 0, boundaryLen); 
       len = input.Read(buffer, boundaryLen, 1024 - boundaryLen) + boundaryLen; 
      } 
     } 
    } 
} 

private static Int32 IndexOf(Byte[] buffer, Int32 len, Byte[] boundaryBytes) 
{ 
    for (Int32 i = 0; i <= len - boundaryBytes.Length; i++) 
    { 
     Boolean match = true; 
     for (Int32 j = 0; j < boundaryBytes.Length && match; j++) 
     { 
      match = buffer[i + j] == boundaryBytes[j]; 
     } 

     if (match) 
     { 
      return i; 
     } 
    } 

    return -1; 
} 

para ayudarle a entender mejor lo que el código anterior está haciendo, esto es lo que el cuerpo del HTTP POST se parece a:

Content-Type: multipart/form-data; boundary=----WebKitFormBoundary9lcB0OZVXSqZLbmv 

------WebKitFormBoundary9lcB0OZVXSqZLbmv 
Content-Disposition: form-data; name="my_file"; filename="Test.txt" 
Content-Type: text/plain 

Test 
------WebKitFormBoundary9lcB0OZVXSqZLbmv-- 

He dejado de lado las cabeceras irrelevantes. Como puede ver, necesita analizar el cuerpo escaneando para encontrar las secuencias de límite iniciales y finales, y soltar los encabezados secundarios que vienen antes del contenido de su archivo. Lamentablemente, no puede usar StreamReader debido a la posibilidad de datos binarios. También desafortunado es el hecho de que no hay por archivo Content-Length (el encabezado Content-Length para la solicitud especifica la longitud total del cuerpo, incluidos los límites, subcanales y espaciado.

+0

Lo probé en un archivo JPEG y en un archivo de texto y ambos funcionaron. –

+0

Hola, gracias por esta respuesta elaborada! ¡De hecho está funcionando y me ayuda mucho! (incluso si esta no es la forma más eficiente) – cecemel

+0

Eso es lo que estoy buscando. Gracias – fyasar

1

El problema es que está leyendo el archivo como texto.

que necesita para leer el archivo como un bytearray lugar y utilizando el BinaryReader es mejor y más fácil de usar que StreamReader:

Byte[] bytes; 
using (System.IO.BinaryReader r = new System.IO.BinaryReader(request.InputStream)) 
{ 
    // Read the data from the stream into the byte array 
    bytes = r.ReadBytes(Convert.ToInt32(request.InputStream.Length)); 
} 
MemoryStream mstream = new MemoryStream(bytes); 
+0

Hola, gracias por la respuesta! sin embargo, parece que el request.InputStream.Length proporciona una excepción no soportado. Además, bytearray complica el trabajo ya que hay algunos que analizar necesidad s para llevarse a cabo para extraer el archivo de otro contenido del cuerpo. Streamrider parecía, desde esa perspectiva, más adecuado para el propósito. – cecemel

+0

@ user1092608: Oh, eso es desafortunado. El problema es que no puedes usar las capacidades de lectura de texto de streamreader para extraer un archivo binario. ¿Estás utilizando un control FileUpload u otro método para insertar el archivo en la transmisión? –

+0

Esa es una observación pertinente. En este momento, el cliente básicamente utiliza el formulario predeterminado html . Si es posible, me gustaría evitar el control fileUpload, para mantener las cosas lo más simples posible, por más que esto siga significando. En este proyecto, otra instancia (máquina) debe ser capaz de lanzar archivos al azar en mi servidor, junto con algunos metadatos, para hacer cosas con él y publicar los resultados de vuelta. – cecemel

1

Puede tener errores, pruebe a fondo . Éste recibe toda POST, GET, y los archivos.

using System.Collections.Generic; 
using System.Collections.Specialized; 
using System.IO; 
using System.Net; 
using System.Text; 
using System.Web; 

namespace DUSTLauncher 
{ 
    class HttpNameValueCollection 
    { 
     public class File 
     { 
      private string _fileName; 
      public string FileName { get { return _fileName ?? (_fileName = ""); } set { _fileName = value; } } 

      private string _fileData; 
      public string FileData { get { return _fileData ?? (_fileName = ""); } set { _fileData = value; } } 

      private string _contentType; 
      public string ContentType { get { return _contentType ?? (_contentType = ""); } set { _contentType = value; } } 
     } 

     private NameValueCollection _get; 
     private Dictionary<string, File> _files; 
     private readonly HttpListenerContext _ctx; 

     public NameValueCollection Get { get { return _get ?? (_get = new NameValueCollection()); } set { _get = value; } } 
     public NameValueCollection Post { get { return _ctx.Request.QueryString; } } 
     public Dictionary<string, File> Files { get { return _files ?? (_files = new Dictionary<string, File>()); } set { _files = value; } } 

     private void PopulatePostMultiPart(string post_string) 
     { 
      var boundary_index = _ctx.Request.ContentType.IndexOf("boundary=") + 9; 
      var boundary = _ctx.Request.ContentType.Substring(boundary_index, _ctx.Request.ContentType.Length - boundary_index); 

      var upper_bound = post_string.Length - 4; 

      if (post_string.Substring(2, boundary.Length) != boundary) 
       throw (new InvalidDataException()); 

      var raw_post_strings = new List<string>(); 
      var current_string = new StringBuilder(); 

      for (var x = 4 + boundary.Length; x < upper_bound; ++x) 
      { 
       if (post_string.Substring(x, boundary.Length) == boundary) 
       { 
        x += boundary.Length + 1; 
        raw_post_strings.Add(current_string.ToString().Remove(current_string.Length - 3, 3)); 
        current_string.Clear(); 
        continue; 
       } 

       current_string.Append(post_string[x]); 

       var post_variable_string = current_string.ToString(); 

       var end_of_header = post_variable_string.IndexOf("\r\n\r\n"); 

       if (end_of_header == -1) throw (new InvalidDataException()); 

       var filename_index = post_variable_string.IndexOf("filename=\"", 0, end_of_header); 
       var filename_starts = filename_index + 10; 
       var content_type_starts = post_variable_string.IndexOf("Content-Type: ", 0, end_of_header) + 14; 
       var name_starts = post_variable_string.IndexOf("name=\"") + 6; 
       var data_starts = end_of_header + 4; 

       if (filename_index == -1) continue; 

       var filename = post_variable_string.Substring(filename_starts, post_variable_string.IndexOf("\"", filename_starts) - filename_starts); 
       var content_type = post_variable_string.Substring(content_type_starts, post_variable_string.IndexOf("\r\n", content_type_starts) - content_type_starts); 
       var file_data = post_variable_string.Substring(data_starts, post_variable_string.Length - data_starts); 
       var name = post_variable_string.Substring(name_starts, post_variable_string.IndexOf("\"", name_starts) - name_starts); 
       Files.Add(name, new File() { FileName = filename, ContentType = content_type, FileData = file_data }); 
       continue; 

      } 
     } 

     private void PopulatePost() 
     { 
      if (_ctx.Request.HttpMethod != "POST" || _ctx.Request.ContentType == null) return; 

      var post_string = new StreamReader(_ctx.Request.InputStream, _ctx.Request.ContentEncoding).ReadToEnd(); 

      if (_ctx.Request.ContentType.StartsWith("multipart/form-data")) 
       PopulatePostMultiPart(post_string); 
      else 
       Get = HttpUtility.ParseQueryString(post_string); 

     } 

     public HttpNameValueCollection(ref HttpListenerContext ctx) 
     { 
      _ctx = ctx; 
      PopulatePost(); 
     } 


    } 
} 
+0

Tiene datos de archivo en tipo de cadena. Tenga cuidado, está tratando con archivos binarios y debe considerar archivos grandes. – fyasar

Cuestiones relacionadas