[英]Handle range requests with HttpListener
我編寫了一個自定義HTTP服務器,該服務器對所有內容都適用,直到瀏覽器發出字節范圍的請求為止。 嘗試加載視頻時(似乎並非每次都會加載一定大小的文件),瀏覽器會使用以下標頭請求視頻文件:
method: GET
/media/mp4/32.mp4
Connection - keep-alive
Accept - */*
Accept-Encoding - identity;q=1/*;q=0
Accept-Language - en-us/en;q=0.8
Host - localhost:20809
Referer - ...
Range - bytes=0-
User-Agent - Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.170 Safari/537.36
因此,服務器發送了請求的文件...然后,它立即發出以下請求:
method: GET
/media/mp4/32.mp4
Connection - keep-alive
Accept - */*
Accept-Encoding - identity;q=1/*;q=0
Accept-Language - en-us/en;q=0.8
Host - localhost:20809
Referer - ...
Range - bytes=40-3689973
User-Agent - Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.170 Safari/537.36
因此,我將請求的字節寫入輸出流,但它總是在第二個請求中出錯並出現錯誤。 幾乎就像服務器在瀏覽器發送另一個請求時服務器仍在嘗試發送文件。
由於線程退出或應用程序請求,I / O操作已中止
以下是處理范圍請求的代碼:
public void StartServer()
{
_server = new HttpListener();
_server.Prefixes.Add("http://localhost:" + _port.ToString() + "/");
LogWebserver("Listening...");
_server.Start();
th = new Thread(new ThreadStart(startlistener));
th.Start();
}
private void startlistener()
{
while (true)
{
////blocks until a client has connected to the server
ProcessRequest();
}
}
private void ProcessRequest()
{
var result = _server.BeginGetContext(ListenerCallback, _server);
result.AsyncWaitHandle.WaitOne();
}
private void ListenerCallback(IAsyncResult result)
{
var context = _server.EndGetContext(result);
HandleContext(context);
}
private void HandleContext(HttpListenerContext context)
{
HttpListenerRequest req = context.Request;
...stuff...
using (HttpListenerResponse resp = context.Response)
{
.... stuff....
byte[] buffer = File.ReadAllBytes(localFile);
if (mime.ToString().Contains("video") || mime.ToString().Contains("audio"))
{
resp.StatusCode = 206;
resp.StatusDescription = "Partial Content";
int startByte = -1;
int endByte = -1;
int byteRange = -1;
if (req.Headers.GetValues("Range") != null)
{
string rangeHeader = req.Headers.GetValues("Range")[0].Replace("bytes=", "");
string[] range = rangeHeader.Split('-');
startByte = int.Parse(range[0]);
if (range[1].Trim().Length > 0) int.TryParse(range[1], out endByte);
if (endByte == -1) endByte = buffer.Length;
}
else
{
startByte = 0;
endByte = buffer.Length;
}
byteRange = endByte - startByte;
resp.ContentLength64 = byteRange;
resp.Headers.Add("Accept-Ranges", "bytes");
resp.Headers.Add("Content-Range", string.Format("bytes {0}-{1}/{2}", startByte, byteRange - 1, byteRange));
resp.Headers.Add("X-Content-Duration", "0.0");
resp.Headers.Add("Content-Duration", "0.0");
resp.OutputStream.Write(buffer, startByte, byteRange);/* this is where it gives the IO error */
resp.OutputStream.Close();
resp.Close();
}
else
{
resp.ContentLength64 = buffer.Length;
resp.OutputStream.Write(buffer, 0, buffer.Length);
resp.OutputStream.Close();
resp.Close();
}
}
}
我試過簡單地忽略其中包含范圍的請求,但是雖然未引發任何錯誤,但瀏覽器卻因未下載視頻而引發錯誤。
如何處理這些范圍請求並避免IO錯誤?
我解決了這個問題,但是它涉及廢棄HttpListener
並改用TcpListener
。
我將此處的代碼用作服務器的基礎: http : //www.codeproject.com/Articles/137979/Simple-HTTP-Server-in-C
然后我用以下方法修改了handleGETRequest
函數:
if (mime.ToString().Contains("video") || mime.ToString().Contains("audio"))
{
using (FileStream fs = new FileStream(localFile, FileMode.Open))
{
int startByte = -1;
int endByte = -1;
if (p.httpHeaders.Contains("Range"))
{
string rangeHeader = p.httpHeaders["Range"].ToString().Replace("bytes=", "");
string[] range = rangeHeader.Split('-');
startByte = int.Parse(range[0]);
if (range[1].Trim().Length > 0) int.TryParse(range[1], out endByte);
if (endByte == -1) endByte = (int)fs.Length;
}
else
{
startByte = 0;
endByte = (int)fs.Length;
}
byte[] buffer = new byte[endByte - startByte];
fs.Position = startByte;
int read = fs.Read(buffer,0, endByte-startByte);
fs.Flush();
fs.Close();
p.outputStream.AutoFlush = true;
p.outputStream.WriteLine("HTTP/1.0 206 Partial Content");
p.outputStream.WriteLine("Content-Type: " + mime);
p.outputStream.WriteLine("Accept-Ranges: bytes");
int totalCount = startByte + buffer.Length;
p.outputStream.WriteLine(string.Format("Content-Range: bytes {0}-{1}/{2}", startByte, totalCount - 1, totalCount));
p.outputStream.WriteLine("Content-Length: " + buffer.Length.ToString());
p.outputStream.WriteLine("Connection: keep-alive");
p.outputStream.WriteLine("");
p.outputStream.AutoFlush = false;
p.outputStream.BaseStream.Write(buffer, 0, buffer.Length);
p.outputStream.BaseStream.Flush();
}
}
else
{
byte[] buffer = File.ReadAllBytes(localFile);
p.outputStream.AutoFlush = true;
p.outputStream.WriteLine("HTTP/1.0 200 OK");
p.outputStream.WriteLine("Content-Type: " + mime);
p.outputStream.WriteLine("Connection: close");
p.outputStream.WriteLine("Content-Length: " + buffer.Length.ToString());
p.outputStream.WriteLine("");
p.outputStream.AutoFlush = false;
p.outputStream.BaseStream.Write(buffer, 0, buffer.Length);
p.outputStream.BaseStream.Flush();
}
使用TcpListener
可以避免任何愚蠢的問題導致響應流上的I / O錯誤。
我知道已經晚了,但是我認為您的解決方案存在一些錯誤。 您的代碼不適用於Safari。
Safari總是要求2個字節來檢查,如果失敗則很遺憾沒有信息。 其他瀏覽器的容忍度更高。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.