使用HttpListener处理范围请求

时间:2014-09-17 19:36:12

标签: c# .net http http-headers

我已经编写了一个自定义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错误?

2 个答案:

答案 0 :(得分:2)

我解决了这个问题,但它涉及报废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错误的任何愚蠢问题。

答案 1 :(得分:0)

我知道它来晚了,但是我认为您的解决方案存在一些错误。您的代码不适用于Safari。

  • 仅在需要时发送范围。
  • endbyte不正确,应该为+1
  • 2。totalcount在内容范围内是错误的,应该是
    的字节 整个文件

Safari始终请求2个字节来检查,但不幸的是没有信息。 其他浏览器的容忍度更高。