I've written a custom HTTP server that works fine for everything until the browser makes a request with a byte range. When attempting to load video (seemingly for files above a certain size because this doesn't happen every time), the browser makes a request for the video file with this header:
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
so the server sends the requested file... Then, immediately afterwards, it makes this request:
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
So I write the requested bytes to the output stream but it always errors out on the second request with an error. It's almost like the server is still trying to send the file when the browser sends another request.
The I/O operation has been aborted because of either a thread exit or an application request
Here is the code that handles the range request:
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();
}
}
}
I've tried simply ignoring the request with the range in it, but while no error is thrown, the browser throw an error because the video wasn't downloaded.
How do I handle these range requests and avoid the IO error?
I solved the problem, but it involved scrapping
HttpListener
and usingTcpListener
instead.I used the code here as a basis for the server: http://www.codeproject.com/Articles/137979/Simple-HTTP-Server-in-C
And then I modified the
handleGETRequest
function with this:Using
TcpListener
avoids whatever stupid problem was causing the I/O error on the response stream.I know its late, but i think there are some misstakes at your solution. Your code will not work with Safari.
the whole file
Safari always request 2 bytes to check, unfortunatly without infos if failed. Other browsers are more tolerant.