Httplistener和文件上传

cec*_*mel 11 c# parsing httplistener httplistenerrequest streamreader

我正在尝试从我的网络服务器检索上传的文件.当客户端通过webform(随机文件)发送文件时,我需要解析请求以获取文件并进一步处理.基本上,代码如下:

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
Run Code Online (Sandbox Code Playgroud)

因此,我能够检索文本文件,但对于所有其他文件,它们都已损坏.有人能告诉我如何正确解析这些HttplistnerRequests(或提供轻量级替代)?

Pau*_*ler 15

我认为通过HttpListener使用ASP.Net的内置功能而不是使用ASP而不是必要的东西,而不是使用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;
}
Run Code Online (Sandbox Code Playgroud)

为了帮助您更好地理解上面的代码正在做什么,这里是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--
Run Code Online (Sandbox Code Playgroud)

我遗漏了不相关的标题.如您所见,您需要通过扫描来解析主体以查找开始和结束边界序列,并删除文件内容之前的子标题.遗憾的是,由于二进制数据的潜在可能性,您无法使用StreamReader.同样不幸的是每个文件没有Content-Length(请求的Content-Length标头指定了主体的总长度,包括边界,子标题和间距).


小智 5

您不能使用它StreamReader,因为它旨在读取字节为 UTF8 的流。相反,您想要将流的内容读取到接收缓冲区,删除所有不需要的内容,获取上传文件的文件扩展名,提取上传文件的内容,然后将文件内容保存到新文件。我在这篇文章中显示的代码假设您的表单如下所示:

<form enctype="multipart/form-data" method="POST" action="/uploader">
    <input type="file" name="file">
    <input type="submit">
</form>
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,该代码仅用于处理仅包含该文件的表单。由于无法从 application/x-www-form-urlencoded 表单中提取服务器上文件的内容,因此您必须包含“multipart/form-data”。

首先,对于这种处理上传文件的方法,您首先需要一点代码:

using System;
using System.IO;
using System.Text;
using System.Net;
using System.Collections.Generic;
Run Code Online (Sandbox Code Playgroud)

其次,您需要将 的内容读取request.InputStream到接收缓冲区或byte[]. 我们通过使用浏览器发送的标头byte[]长度创建一个缓冲区来实现此目的。Content-Length然后,我们将内容读取request.InputStream到缓冲区。代码如下所示:

int len = int.Parse(request.Headers["Content-Length"]);
byte[] buffer = new byte[len];
request.InputStream.Read(buffer, 0, len);
Run Code Online (Sandbox Code Playgroud)

该流看起来有点像这样:

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

file contents here
------WebKitFormBoundary9lcB0OZVXSqZLbmv--
Run Code Online (Sandbox Code Playgroud)

接下来,您需要获取上传文件的文件扩展名。我们可以使用以下代码来做到这一点:

string fileExtension = Encoding.UTF8.GetString(bytes).Split("\r\n")[1].Split("filename=\"")[1].Replace("\"", "").Split('.')[^1];
Run Code Online (Sandbox Code Playgroud)

然后,我们需要获取文件的内容。我们通过删除开头的内容(-----WebKitFormBoundary、Content-Disposition、Content-Type 和空行)来实现此目的,然后删除请求正文的最后一行,并\r\n在结尾。这是执行此操作的代码:

// note that the variable buffer is the byte[], and the variable bytes is the List<byte>
string stringBuffer = Encoding.UTF8.GetString(buffer);
List<byte> bytes = new List<byte>(buffer);
string[] splitString = stringBuffer.Split('\n');
int lengthOfFourLines = splitString[0].Length + splitString[1].Length + 
splitString[2].Length + splitString[3].Length + 4;
bytes.RemoveRange(0, lengthOfFourLines);
int lengthOfLastLine = splitString[^2].Length+2;
bytes.RemoveRange(bytes.Count - lengthOfLastLine, lengthOfLastLine);
buffer = bytes.ToArray();
Run Code Online (Sandbox Code Playgroud)

最后,我们需要将内容保存到文件中。下面的代码生成一个带有用户指定的文件扩展名的随机文件名,并将文件内容保存到其中。

string fname = "";
string[] chars = "q w e r t y u i o p a s d f g h j k l z x c v b n m Q W E R T Y U I O P A S D F G H J K L Z X C V B N M 1 2 3 4 5 6 7 8 9 0".Split(" ");
for (int i = 0; i < 10; i++)
{
    fname += chars[new Random().Next(chars.Length)];
}
fname += fileExtension;
FileStream file = File.Create(fname);
file.Write(buffer);
file.Close();
Run Code Online (Sandbox Code Playgroud)

这是完整的代码:

public static void Main()
{
    var listener = new HttpListener();
    listener.Prefixes.Add("http://localhost:8080/");
    listener.Start();
    while(true)
    {
        HttpListenerContext context = listener.GetContext();
        HttpListenerRequest request = context.Request;
        HttpListenerResponse response = context.Response;
        if(request.HttpMethod=="POST") SaveFile(request);
        response.OutputStream.Write(Encoding.UTF8.GetBytes("file successfully uploaded"));
        response.OutputStream.Close();
    }
}
void SaveFile(HttpListenerRequest request)
{
    int len = (int)request.ContentLength64;
    Console.WriteLine(len);
    byte[] buffer = new byte[len];
    request.InputStream.Read(buffer, 0, len);
    string stringBuffer = Encoding.UTF8.GetString(buffer);
    Console.WriteLine(stringBuffer.Replace("\r\n","\\r\\n\n"));
    string fileExtension = stringBuffer.Split("\r\n")[1]
        .Split("filename=\"")[1]
        .Replace("\"", "")
        .Split('.')[^1]
    ;
    List<byte> bytes = new List<byte>(buffer);
    string[] splitString = stringBuffer.Split('\n');
    int lengthOfFourLines = splitString[0].Length + splitString[1].Length + splitString[2].Length + splitString[3].Length + 4;
    bytes.RemoveRange(0, lengthOfFourLines);
    int lengthOfLastLine = splitString[^2].Length+2;
    bytes.RemoveRange(bytes.Count - lengthOfLastLine, lengthOfLastLine);
    buffer = bytes.ToArray();
    
    string fname = "";
    string[] chars = "q w e r t y u i o p a s d f g h j k l z x c v b n m Q W E R T Y U I O P A S D F G H J K L Z X C V B N M 1 2 3 4 5 6 7 8 9 0".Split(" ");
    for (int i = 0; i < 10; i++)
    {
        fname += chars[new Random().Next(chars.Length)];
    }
    fname += "." + fileExtension;
    FileStream file = File.Create(fname);
    file.Write(buffer);
    file.Close();
}
Run Code Online (Sandbox Code Playgroud)

另外,如果您想将上传的文件发送到客户端,这里有一个有用的函数,可以将文件发送到客户端。

// Make sure you are using System.IO, and System.Net when making this function.
// Also make sure you set the content type of the response before calling this function.
// fileName is the name of the file you want to send to the client, and output is the response.OutputStream.
public static void SendFile(string fileName, Stream output)
{
    FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
    fs.CopyTo(output);
    fs.Close();
    output.Close();
}
Run Code Online (Sandbox Code Playgroud)