我正在尝试从我的网络服务器检索上传的文件。当客户端通过网络表单(随机文件)发送其文件时,我需要解析请求以获取文件并进一步处理它。 基本上,代码如下:
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 (或提供轻量级替代方案)吗?
我认为您使用
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。同样不幸的是,没有每个文件的内容长度(请求的内容长度标头指定正文的总长度,包括边界、子标头和间距。
您不能使用
StreamReader
,因为它旨在读取字节为 UTF8 的流。相反,您想要将流的内容读取到接收缓冲区,删除所有不需要的内容,获取上传文件的文件扩展名,提取上传文件的内容,然后将文件内容保存到新文件。我在这篇文章中显示的代码假设您的表单如下所示:
<form enctype="multipart/form-data" method="POST" action="/uploader">
<input type="file" name="file">
<input type="submit">
</form>
如您所见,代码仅用于处理仅包含文件的表单。由于无法从 application/x-www-form-urlencoded 表单中提取服务器上文件的内容,因此您必须包含“multipart/form-data”。
首先,对于这种处理上传文件的方法,您首先需要一点代码:
using System;
using System.IO;
using System.Text;
using System.Net;
using System.Collections.Generic;
其次,您需要将
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);
该流看起来有点像这样:
------WebKitFormBoundary9lcB0OZVXSqZLbmv
Content-Disposition: form-data; name="file"; filename="example-file.txt"
Content-Type: text/plain
file contents here
------WebKitFormBoundary9lcB0OZVXSqZLbmv--
接下来,您需要获取上传文件的文件扩展名。我们可以使用以下代码来做到这一点:
string fileExtension = Encoding.UTF8.GetString(bytes).Split("\r\n")[1].Split("filename=\"")[1].Replace("\"", "").Split('.')[^1];
然后,我们需要获取文件的内容。我们通过删除开头的内容(-----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();
最后,我们需要将内容保存到文件中。下面的代码生成一个带有用户指定的文件扩展名的随机文件名,并将文件内容保存到其中。
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();
这是完整的代码:
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();
}
此外,如果您想将上传的文件发送到客户端,这里有一个有用的函数,可以将文件发送到客户端。
// 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();
}
问题是您正在以文本形式读取文件。
您需要将文件作为字节数组读取,并且使用 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);
可能有错误,请彻底测试。这个获取所有帖子、获取和文件。
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();
}
}
}
我喜欢@paul-wheeler 的回答。但是我需要修改他们的代码以包含一些附加数据(在本例中为目录结构)。
我正在使用此代码上传文件:
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 修改后的代码。谢谢@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];
}
}
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Net.Http.Headers;
var contentType = MediaTypeHeaderValue.Parse(context.Request.ContentType);
var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary).Value;
var multipartReader = new MultipartReader(boundary, context.Request.InputStream);
var section = (await multipartReader.ReadNextSectionAsync()).AsFileSection();
var fileName = section.FileName;
var fileStream = section.FileStream;
对于给定的
HttpListenerRequest req
和 string path
,以下代码保存一个文件(适用于文本文件,也适用于 png 文件)
int len = (int)req.ContentLength64;
byte[] buffer = new byte[len];
int totalRead = 0;
while(totalRead < len){
// InputStream.Read does not read always read full stream (so loop until it has)
totalRead += req.InputStream.Read(buffer, totalRead, len - totalRead);
}
string stringBuffer = Encoding.UTF8.GetString(buffer);
string startTag = stringBuffer.Substring(0, stringBuffer.IndexOf("\r\n\r\n") + 4);
string endTag = stringBuffer.Substring(stringBuffer.IndexOf("\r\n------WebKitFormBoundary"));
List<byte> bytes = new List<byte>(buffer);
bytes = bytes.GetRange(startTag.Length, len - (startTag.Length + endTag.Length));
buffer = bytes.ToArray();
File.WriteAllBytes(path, buffer);
(建立在 copee moo 解决方案之上)
static string ReadFileContentNullable(in string header)
{
if (header == null)
return null;
unchecked
{
const short maxHeaderSizeBytes = 2048;
const byte zero = 0, six = 6, one = 1, two = 2;
const char r = '\r', n = '\n';
const string mime = "text/plain";
int? len = header?.Length;
if (len == null || len < six || len > maxHeaderSizeBytes / sizeof(char))
return null;
bool found = false, equal = false;
string boundary = "=----";
int i = zero, patternLength = boundary.Length, j = zero, k = zero, l = patternLength - one;
while (i != len && !found)
{
j = i + l;
++i;
k = l;
equal = true;
while(k > zero && equal)
if (header[j--] != boundary[k--])
equal = false;
if (equal)
found = true;
}
if (i == len)
return null;
StringBuilder sb = new (len.Value);
sb.Clear();
sb.Append("--");
while(i != len && (header[i + one] != r || header[i + two] != n))
sb.Append(header[i++]);
sb.Append(header[i]);
if (i == len)
return null;
if (++i == len)
return null;
boundary = sb.ToString();
sb.Clear();
while(i != len && (header[i] == r || header[i] == n))
++i;
if (++i == len)
return null;
patternLength = boundary.Length;
i += patternLength + one;
if (i + patternLength + one > len)
return null;
patternLength = mime.Length;
if (i + patternLength + one > len)
return null;
found = false;
equal = true;
while (i != len && !found)
{
j = i + patternLength - one;
++i;
k = patternLength - one;
equal = true;
while(k > zero && equal)
if (header[j--] != mime[k--])
equal = false;
if (equal)
found = true;
}
if (!found)
return null;
i += patternLength + one;
while(i != len && (header[i] == r || header[i] == n))
++i;
if (i == len)
return null;
found = false;
equal = true;
boundary = string.Concat("\r\n", boundary, "--");
patternLength = boundary.Length;
while (i != len && !found)
{
j = i + patternLength;
sb.Append(header[i++]);
k = patternLength - one;
equal = true;
while(k > zero && equal)
if (header[j--] != boundary[k--])
equal = false;
if (equal)
found = true;
}
if (!found)
return null;
return sb.ToString();
}
}
它是我的宠物项目中调整后的代码。调用约定:
string result = ReadFileContentNullable(context?.Request?.ContentType);
if (!string.IsNullOrWhiteSpace(result))
System.Console.WriteLine("success");