-
-
Save vsajip/2f4df2dafc3ecfb0f450 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Copyright 2009 Vinay Sajip | |
// | |
// This file is free software: you can redistribute it and/or modify it under the terms of the | |
// GNU General Public License as published by the Free Software Foundation, either version 3 of | |
// the License, or (at your option) any later version. | |
// | |
// This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; | |
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | |
// See the GNU General Public License for more details. | |
// | |
// See http://www.gnu.org/licenses/ for more information. | |
// | |
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Net; | |
using System.Text; | |
using System.Text.RegularExpressions; | |
using System.Threading; | |
namespace HttpListenerProxy | |
{ | |
class ProxyListener | |
{ | |
private const int CHUNK_SIZE = 1024; | |
private Uri _uri; | |
private int _port; | |
private string _fileName; | |
private string _fileNameInUse; | |
private bool _downloaded; | |
private string _mimeType; | |
private HttpListener _httpListener; | |
private Thread _listener; | |
private Thread _downloader; | |
private FileStream _stream; | |
private List<byte[]> _chunks; | |
private AutoResetEvent _chunkAvailable; | |
private AutoResetEvent _downloadStarted; | |
private AutoResetEvent _downloadComplete; | |
private long _downloadSize; | |
private WaitHandle[] _events; | |
private static void Log(string message) | |
{ | |
Console.WriteLine(message); | |
} | |
internal ProxyListener(Uri uri, int port, string fileName) | |
{ | |
_uri = uri; | |
_port = port; | |
_fileName = fileName; | |
_downloaded = File.Exists(fileName); | |
_listener = new Thread(Listen); | |
if (_downloaded) | |
_fileNameInUse = fileName; | |
else | |
{ | |
_fileNameInUse = String.Format("{0}.partial", fileName); | |
_downloader = new Thread(Download); | |
_chunkAvailable = new AutoResetEvent(false); | |
_downloadStarted = new AutoResetEvent(false); | |
_downloadComplete = new AutoResetEvent(false); | |
_events = new WaitHandle[] { _chunkAvailable, _downloadComplete }; | |
} | |
} | |
internal void Start() | |
{ | |
if (_downloader != null) | |
_downloader.Start(); | |
else | |
_stream = new FileStream(_fileName, FileMode.Open, FileAccess.Read); | |
_listener.Start(); | |
} | |
internal void Cleanup() | |
{ | |
_httpListener.Close(); | |
_listener.Abort(); | |
_listener.Join(); | |
if ((_downloader != null) && _downloader.IsAlive) | |
{ | |
_downloader.Abort(); | |
_downloader.Join(); | |
} | |
if (_chunks != null) | |
{ | |
lock (_chunks) | |
{ | |
_chunks.Clear(); | |
} | |
} | |
} | |
void Download() | |
{ | |
Log("downloader: starting"); | |
_stream = new FileStream(_fileNameInUse, FileMode.OpenOrCreate, FileAccess.Write); | |
try | |
{ | |
_chunks = new List<byte[]>(); | |
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(_uri); | |
HttpWebResponse response = (HttpWebResponse)request.GetResponse(); | |
_mimeType = response.ContentType; | |
_downloadSize = response.ContentLength; | |
Log(String.Format("Content-type: {0}, Content-Length: {1}", _mimeType, _downloadSize)); | |
_downloadStarted.Set(); | |
Stream stream = response.GetResponseStream(); | |
byte[] buffer = new byte[CHUNK_SIZE]; | |
long bytesRead = 0; | |
for (; ; ) | |
{ | |
int count = stream.Read(buffer, 0, CHUNK_SIZE); | |
int chunkIndex; | |
if (count <= 0) | |
break; | |
bytesRead += count; | |
_stream.Write(buffer, 0, count); | |
_stream.Flush(); | |
byte[] chunk = new byte[count]; | |
Array.Copy(buffer, chunk, count); | |
lock (_chunks) | |
{ | |
_chunks.Add(chunk); | |
chunkIndex = _chunks.Count; | |
_chunkAvailable.Set(); | |
} | |
Log(String.Format("downloader: {0} bytes read, added at {1}", bytesRead, chunkIndex - 1)); | |
} | |
_stream.Close(); | |
File.Move(_fileNameInUse, _fileName); | |
_downloadComplete.Set(); | |
_downloaded = true; | |
} | |
finally | |
{ | |
Log("downloader: terminating"); | |
} | |
} | |
void Listen() | |
{ | |
_httpListener = new HttpListener(); | |
_httpListener.Prefixes.Add(String.Format("http://*:{0}/", _port)); | |
_httpListener.Start(); | |
try | |
{ | |
for (; ; ) | |
{ | |
HttpListenerContext context = _httpListener.GetContext(); | |
HttpListenerResponse response = context.Response; | |
response.StatusCode = 200; | |
response.StatusDescription = "OK"; | |
if (!_downloaded && (_mimeType == null)) | |
_downloadStarted.WaitOne(); | |
// If we already have the file, we don't know its mime type - stick in a default value for IE | |
response.ContentType = _mimeType ?? "application/octet-stream"; | |
Log(String.Format("uploader: setting content type to {0}", response.ContentType)); | |
Stream stream = response.OutputStream; | |
if (_downloaded) | |
{ | |
response.ContentLength64 = _stream.Length; | |
byte[] buffer = new byte[CHUNK_SIZE]; | |
_stream.Seek(0, SeekOrigin.Begin); | |
for (; ; ) | |
{ | |
int count = _stream.Read(buffer, 0, CHUNK_SIZE); | |
if (count <= 0) | |
break; | |
stream.Write(buffer, 0, count); | |
} | |
stream.Close(); | |
} | |
else | |
{ | |
response.ContentLength64 = _downloadSize; | |
long bytesRead = 0; | |
int chunkIndex = 0; | |
int numChunks; | |
try | |
{ | |
byte[] chunk; | |
// write out any chunks we already have | |
lock (_chunks) | |
{ | |
numChunks = _chunks.Count; | |
} | |
for (; chunkIndex < numChunks; chunkIndex++) | |
{ | |
lock (_chunks) | |
{ | |
chunk = _chunks[chunkIndex]; | |
stream.Write(chunk, 0, chunk.Length); | |
bytesRead += chunk.Length; | |
} | |
Log(String.Format("uploader: {0} bytes read from {1}", bytesRead, chunkIndex)); | |
} | |
// reached end of chunks. Wait for more | |
while (WaitHandle.WaitAny(_events) != 1) | |
{ | |
lock (_chunks) | |
{ | |
numChunks = _chunks.Count; | |
} | |
for (; chunkIndex < numChunks; chunkIndex++) | |
{ | |
lock (_chunks) | |
{ | |
chunk = _chunks[chunkIndex]; | |
stream.Write(chunk, 0, chunk.Length); | |
bytesRead += chunk.Length; | |
} | |
Log(String.Format("uploader: {0} bytes read from {1}", bytesRead, chunkIndex)); | |
} | |
} | |
stream.Close(); | |
} | |
catch (HttpListenerException e) | |
{ | |
Log("uploader: client closed connection: " + e.Message); | |
} | |
} | |
} | |
} | |
catch (HttpListenerException ex) | |
{ | |
Log(String.Format("uploader: exception: {0}", ex.Message)); | |
} | |
finally | |
{ | |
if (_httpListener.IsListening) | |
_httpListener.Close(); | |
} | |
} | |
} | |
class Program | |
{ | |
private static Regex LINE_MATCHER = new Regex(@"^(\S+)\s+(\d+)\s+(\S+)$"); | |
private Dictionary<string, ProxyListener> mappings = new Dictionary<string, ProxyListener>(); | |
private void ProxyUri(Uri uri, int port, string cacheFile) | |
{ | |
ProxyListener listener = new ProxyListener(uri, port, cacheFile); | |
string key = String.Format("{0}|{1}|{2}", uri, port, cacheFile); | |
if (mappings.ContainsKey(key)) | |
{ | |
mappings[key].Cleanup(); | |
} | |
mappings[key] = listener; | |
listener.Start(); | |
} | |
private void Cleanup() | |
{ | |
foreach (ProxyListener listener in mappings.Values) | |
{ | |
listener.Cleanup(); | |
} | |
} | |
private void ProcessLine(string line) | |
{ | |
Match m = LINE_MATCHER.Match(line); | |
bool notUnderstood = false; | |
if (!m.Success) | |
{ | |
notUnderstood = true; | |
} | |
else | |
{ | |
try | |
{ | |
Uri uri = new Uri(m.Groups[1].Value); | |
int port = Convert.ToInt32(m.Groups[2].Value); | |
string fileName = m.Groups[3].Value; | |
//Console.WriteLine(String.Format("Understood: {0}, {1}, {2}", uri, port, fileName)); | |
ProxyUri(uri, port, fileName); | |
} | |
catch (UriFormatException) | |
{ | |
notUnderstood = true; | |
} | |
} | |
if (notUnderstood) | |
Console.WriteLine("Not understood. Enter URI, port and filename separated by spaces"); | |
} | |
private void Run(string[] args) | |
{ | |
List<string> prefetched = new List<string>(); | |
// Stick in here any pre-canned lines you want to test with... | |
//prefetched.Add(@"http://media.railscasts.com/videos/176_searchlogic.mov 8000 c:\temp\temp.mov"); | |
try | |
{ | |
foreach (string line in prefetched) | |
ProcessLine(line); | |
Console.WriteLine("Enter URI, port and filename separated by spaces:"); | |
for (; ; ) | |
{ | |
string line = Console.ReadLine(); | |
if (line.Length == 0) | |
break; | |
ProcessLine(line); | |
} | |
Console.Write("All done. Press the Enter key to exit:"); | |
Console.ReadKey(); | |
} | |
finally | |
{ | |
Cleanup(); | |
} | |
} | |
static void Main(string[] args) | |
{ | |
new Program().Run(args); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment