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)