Httplistener и загрузка файлов

Я пытаюсь загрузить загруженный файл с моего веб-сервера. Поскольку клиент отправляет свои файлы через веб-форму (случайные файлы), мне нужно разобрать запрос, чтобы получить файл и обработать его дальше. В основном код выглядит следующим образом:

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

В результате я могу получить текстовые файлы, но для всех других файлов они повреждены. Может ли кто-нибудь сказать мне, как правильно разбирать эти HttplistnerRequests (или предоставить альтернативу с облегченным весом)?

Ответ 1

Я думаю, что вы делаете все сложнее, чем нужно, делая это с помощью HttpListener, а не используя встроенные средства ASP.Net. Но если вы должны это сделать, вот пример кода. Примечание: 1) Предполагаю, что вы используете enctype="multipart/form-data" на своем <form>. 2) Этот код предназначен для использования с формой, содержащей только ваш <input type="file" />, если вы хотите публиковать другие поля или несколько файлов, которые вам придется изменить. 3) Это должно быть доказательством концепции/примера, оно может иметь ошибки и не особенно гибко.

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;
}

Чтобы помочь вам лучше понять, что делает вышеприведенный код, вот что выглядит тело HTTP POST:

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

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

Test
------WebKitFormBoundary9lcB0OZVXSqZLbmv--

Я упустил ненужные заголовки. Как вы можете видеть, вам нужно проанализировать тело, просматривая его, чтобы найти начальную и конечную граничные последовательности, и отбросьте заголовки, которые появляются перед содержимым вашего файла. К сожалению, вы не можете использовать StreamReader из-за возможности бинарных данных. Также печально, что нет никакого файла Content-Length (заголовок Content-Length для запроса указывает общую длину тела, включая границы, подзаголовки и интервал.

Ответ 2

Проблема в том, что вы читаете файл как текст.

Вам нужно прочитать файл как bytearray, а использовать BinaryReader лучше и проще в использовании, чем 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);

Ответ 3

Может быть, ошибки, тщательно протестируйте. Этот получает все сообщения, файлы и файлы.

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();
        }


    }
}

Ответ 4

Мне нравится @Пол-Уилер ответ. Однако мне нужно было изменить их код для включения некоторых дополнительных данных (в данном случае, структуры каталогов).

Я использую этот код для загрузки файлов:

var myDropzone = $("#fileDropZone");
myDropzone.dropzone(
    {
        url: "http://" + self.location.hostname + "/Path/Files.html,
        method: "post",
        createImageThumbnails: true,
        previewTemplate: document.querySelector('#previewTemplateId').innerHTML,
        clickable: false,
        init: function () {
            this.on('sending', function(file, xhr, formData){
                // xhr is XMLHttpRequest
                var name = file.fullPath;
                if (typeof (file.fullPath) === "undefined") {
                    name = file.name;
                }

                formData.append('fileNameWithPath', name);
            });
        }
    });

Вот модифицированный код @paul-wheeler. Спасибо, Пол-Уилер.

public class FileManager
{
    public static void SaveFile(HttpListenerRequest request, string savePath)
    {
        var tempFileName = Path.Combine(savePath, $"{DateTime.Now.Ticks}.tmp");
        if (!Directory.Exists(savePath))
        {
            Directory.CreateDirectory(savePath);
        }

        var (res, fileName) = SaveTmpFile(request, tempFileName);
        if (res)
        {
            var filePath = Path.Combine(savePath, fileName);
            var fileDir = filePath.Substring(0, filePath.LastIndexOf(Path.DirectorySeparatorChar));
            if (!Directory.Exists(fileDir))
            {
                Directory.CreateDirectory(fileDir);
            }

            if (File.Exists(filePath))
            {
                File.Delete(filePath);
            }

            File.Move(tempFileName, filePath);
        }
    }

    private static (bool, string) SaveTmpFile(HttpListenerRequest request, string tempFileName)
    {
        var enc = request.ContentEncoding;
        var boundary = GetBoundary(request.ContentType);
        var input = request.InputStream;

        byte[] boundaryBytes = enc.GetBytes(boundary);
        var boundaryLen = boundaryBytes.Length;

        using (FileStream output = new FileStream(tempFileName, FileMode.Create, FileAccess.Write))
        {
            var buffer = new byte[1024];
            var len = input.Read(buffer, 0, 1024);
            var startPos = -1;

            // Get file name and relative path
            var strBuffer = Encoding.Default.GetString(buffer);
            var strStart = strBuffer.IndexOf("fileNameWithPath") + 21;
            if (strStart < 21)
            {
                Logger.LogError("File name not found");
                return (false, null);
            }

            var strEnd = strBuffer.IndexOf(boundary, strStart) - 2;
            var fileName = strBuffer.Substring(strStart, strEnd - strStart);
            fileName = fileName.Replace('/', Path.DirectorySeparatorChar);

            // Find start boundary
            while (true)
            {
                if (len == 0)
                {
                    Logger.LogError("Find start boundary not found");
                    return (false, null);
                }

                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);
                }
            }

            // Advance to data
            var foundData = false;
            while (!foundData)
            {
                while (true)
                {
                    if (len == 0)
                    {
                        Logger.LogError("Preamble not Found");
                        return (false, null);
                    }

                    startPos = Array.IndexOf(buffer, enc.GetBytes("\n")[0], startPos);
                    if (startPos >= 0)
                    {
                        startPos++;
                        break;
                    }
                    else
                    {
                        // In case read in line is longer than buffer
                        len = input.Read(buffer, 0, 1024);
                    }
                }

                var currStr = Encoding.Default.GetString(buffer).Substring(startPos);
                if (currStr.StartsWith("Content-Type:"))
                {
                    // Go past the last carriage-return\line-break. (\r\n)
                    startPos = Array.IndexOf(buffer, enc.GetBytes("\n")[0], startPos) + 3;
                    break;
                }
            }

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

            while (true)
            {
                var endPos = IndexOf(buffer, len, boundaryBytes);
                if (endPos >= 0)
                {
                    if (endPos > 0) output.Write(buffer, 0, endPos - 2);
                    break;
                }
                else if (len <= boundaryLen)
                {
                    Logger.LogError("End Boundaray Not Found");
                    return (false, null);
                }
                else
                {
                    output.Write(buffer, 0, len - boundaryLen);
                    Array.Copy(buffer, len - boundaryLen, buffer, 0, boundaryLen);
                    len = input.Read(buffer, boundaryLen, 1024 - boundaryLen) + boundaryLen;
                }
            }

            return (true, fileName);
        }
    }

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

            if (match)
            {
                return i;
            }
        }

        return -1;
    }

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