C# 㨠.NET Framework ã§ä½ãç°¡åãããã·ãµã¼ã
ååãããå®å®ãã¦åä½ããããã«ãªãã¾ããã
ãã®ããã°ã©ã 㯠.NET Framework ã§ç¨æããã¦ãã HttpListener ã¯ã©ã¹ã¨ HttpWebRequest ã使ã£ã¦ãããã·ãµã¼ã (Proxy Server) ãå®ç¾ãã¾ãã
System.Net.HttpListener ã®ä»æ§ä¸ããã©ãããã©ã¼ã ã«å¶éãããã¾ãã
ãã®ã¯ã©ã¹ã¯ãWindows XP SP2 ã¾ã㯠Windows Server 2003 ã®ãªãã¬ã¼ãã£ã³ã° ã·ã¹ãã ãå®è¡ãã¦ããã³ã³ãã¥ã¼ã¿ã§ãã使ç¨ã§ãã¾ããããã以åã®ãªãã¬ã¼ãã£ã³ã° ã·ã¹ãã ãå®è¡ãã¦ããã³ã³ãã¥ã¼ã¿ã§ HttpListener ãªãã¸ã§ã¯ããä½æãããã¨ããã¨ãã³ã³ã¹ãã©ã¯ã¿ãã PlatformNotSupportedException ä¾å¤ãã¹ãã¼ããã¾ãã
é常ã«ãããã¼ãªä½ãã«ãªã£ã¦ãã¾ãã®ã§ãå®ç¨çã«ä½¿ããã¨ã¯ã§ãã¾ãããããã³ãã³åç»ã YouTube ã®ãã£ãã·ã¥ãµã¼ããªã©ã¯å®ç¾å¯è½ãã¨æããã¾ãã
ããããã£ã«ã¿ã HTML ã®æ¸ãæããªã©ãå®ç¾ã§ããã¨ãããã¾ãã
以ä¸ã½ã¼ã¹ã³ã¼ãã
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Net; using System.Diagnostics; using System.IO; namespace Samples { class HttpServer { HttpListener listener; public bool IsListening { get { return (listener != null) && listener.IsListening; } } public HttpServer() { listener = null; } public void Start() { if (listener != null) return; Debug.WriteLine("Enter HttpServer::Start"); listener = new HttpListener(); listener.Prefixes.Add(string.Format("http://{0}:{1}/", IPAddress.Loopback, 8081)); listener.Start(); listener.BeginGetContext(EndGetContext, listener); Debug.WriteLine("Exit HttpServer::Start"); } public void Stop() { if (listener == null) return; Debug.WriteLine("Enter HttpServer::Stop"); listener.Stop(); listener.Close(); listener = null; Debug.WriteLine("Exit HttpServer::Stop"); } private void EndGetContext(IAsyncResult ar) { Debug.WriteLine("Enter HttpServer::EndGetContext"); HttpListener listener = ar.AsyncState as HttpListener; if (!listener.IsListening) // å¼ã³åºãã listener ã Stop ããã¦ãããªãä½ãããªã { Debug.WriteLine("Exit HttpServer::EndGetContext listener stopped"); return; } HttpListenerContext context = null; try { listener.BeginGetContext(EndGetContext, listener); context = listener.EndGetContext(ar); HandleRequest(context); } catch (Exception ex) { Debug.WriteLine("Exception HttpServer::EndGetContext " + ex.Message); if (context != null) context.Response.Abort(); } finally { if (context != null) context.Response.Close(); Debug.WriteLine("Exit HttpServer::EndGetContext"); } } private HttpWebRequest CreateRequest(HttpListenerContext context, bool keepalive) { // HttpListenerRequest ã¨åã HttpWebRequest ãä½ãã HttpListenerRequest req = context.Request; HttpWebRequest webRequest = WebRequest.Create(req.RawUrl) as HttpWebRequest; webRequest.Method = req.HttpMethod; webRequest.ProtocolVersion = HttpVersion.Version11; // æ¥ç¶ãã¦ããã¯ã©ã¤ã¢ã³ããåæãã¦ããã»ãã®ã¯ã©ã¤ã¢ã³ãã§ãã® WebRequest ã®æ¥ç¶ãåå©ç¨ã§ããã¯ããªã®ã§å¸¸ã« true ã§ããããï¼ webRequest.KeepAlive = keepalive; // HttpWebRequest ã®å¶éããã¤ãã®ã§ããããã¨ã«å¯¾å¿ for (int i = 0; i < req.Headers.Count; i++) { string name = req.Headers.GetKey(i).ToLower(); string value = req.Headers.Get(i).ToLower(); switch (name) { case "host": break; // WebRequest.Create ã§é©åã«è¨å®ããã¦ããã¯ã ãã¨ã§ç¢ºèª case "connection": case "proxy-connection": webRequest.KeepAlive = keepalive; // TODO: keepalive ã®åå¾ã¯ããã§è¡ãã break; case "referer": webRequest.Referer = value; break; case "user-agent": webRequest.UserAgent = value; break; case "accept": webRequest.Accept = value; break; case "content-length": webRequest.ContentLength = req.ContentLength64; break; case "content-type": webRequest.ContentType = value; break; case "if-modified-since": webRequest.IfModifiedSince = DateTime.Parse(value); break; default: try { // ãã®ä»ãä¸ä»¥å¤ã«ãåå¥ã«å¯¾å¿ããªããã°ãªããªããã®ããããé¢åãªã®ã§ãã¹ webRequest.Headers.Set(name, value); } catch { Debug.WriteLine("Exception HttpServer::CreateRequest header=" + name); } break; } } return webRequest; } private void HandleRequest(HttpListenerContext context) { // ã©ãããå¦çããããã®ãã決ããã HttpListenerRequest req = context.Request; HttpListenerResponse res = context.Response; // ã©ãããæ¥ç¶ããããã¨ãå å·¥ããã¦ããªãã¢ãã¬ã¹ Debug.WriteLine("Info HttpServer::HandleRequest " + string.Format("UserHost={0}: Request={1}", req.UserHostAddress, req.RawUrl)); bool keepalive = req.KeepAlive; // 常㫠false ãããããã°ãããã if (!string.IsNullOrEmpty(req.Headers["Connection"]) && req.Headers["Connection"].IndexOf("keep-alive", StringComparison.InvariantCultureIgnoreCase) >= 0 || !string.IsNullOrEmpty(req.Headers["Proxy-Connection"]) && req.Headers["Proxy-Connection"].IndexOf("keep-alive", StringComparison.InvariantCultureIgnoreCase) >= 0) keepalive = true; // ãã°å¯¾çï¼ if (req.RawUrl.StartsWith("/") || req.RawUrl.StartsWith("http://local.ptron/")) ProcessLocalRequest(context, keepalive); else // ããã¯ã·ãµã¼ãã¨ãã¦ã®æ¯ãèã ProcessProxyRequest(context, keepalive); } private void ProcessProxyRequest(HttpListenerContext context, bool keepalive) { // ãããã·ãµã¼ãã¨ãã¦ã®åä½ã HttpListenerRequest req = context.Request; HttpListenerResponse res = context.Response; HttpWebRequest webRequest = CreateRequest(context, keepalive); // ããã£ãã£ããéåä¿¡ if (req.HasEntityBody) // ãªã¯ã¨ã¹ãã®ããã£ããã (POST ã¨ã) Relay(req.InputStream, webRequest.GetRequestStream()); // ã¬ã¹ãã³ã¹åå¾ HttpWebResponse webResponse = null; try { webResponse = webRequest.GetResponse() as HttpWebResponse; } catch (WebException e) { webResponse = e.Response as HttpWebResponse; // ã¬ã¹ãã³ã¹ãããã°ã¨ãã304 ã¨ãã®å ´åããªããã° null ã«ãªãã Debug.WriteLine("Exception HttpServer::ProcessProxyRequest " + e.Message); } // ã ãã ã£ãæã®å¦çãã¦ãã¨ã if (webResponse == null) { SendResponse(context, 502, "Bad Gateway", keepalive, null); return; } // ãã©ã¦ã¶ã¸è¿ãã¬ã¹ãã³ã¹ã®è¨å®ãããã¦ãã©ã res.ProtocolVersion = HttpVersion.Version11; // 常㫠HTTP/1.1 ã¨ãã¦ãã res.StatusCode = (int)webResponse.StatusCode; res.StatusDescription = webResponse.StatusDescription; res.KeepAlive = keepalive; for (int i = 0; i < webResponse.Headers.Count; i++) { string name = webResponse.Headers.GetKey(i).ToLower(); string value = webResponse.Headers.Get(i).ToLower(); switch (name) { case "content-length": res.ContentLength64 = webResponse.ContentLength; break; case "keep-alive": // ã©ããã£ã¦è¨å®ãããã... Debug.WriteLine("Info HttpServer::ProcessProxyRequest keep-alive: " + value); break; case "transfer-encoding": res.SendChunked = value.IndexOf("chunked") >= 0 ? true : false; break; default: try { res.Headers.Set(name, value); } catch { Debug.WriteLine("Exception HttpServer::ProcessProxyRequest header=" + name); } break; } } Relay(webResponse.GetResponseStream(), res.OutputStream); webResponse.Close(); } private void ProcessLocalRequest(HttpListenerContext context, bool keepalive) { HttpListenerRequest req = context.Request; // é常㮠HTTP ãµã¼ãã¨ãã¦ã®æ¯ãèããã¾ã㯠local.ptron ã¸ã®ã¢ã¯ã»ã¹ã if (req.RawUrl.Equals("/") || req.RawUrl.Equals("http://local.ptron/")) SendResponse(context, 200, "OK", keepalive, Encoding.Default.GetBytes("Hello World")); else // favicon ã¨ãåãã«æ¥ãã®ã§ã SendResponse(context, 404, "Not Found", keepalive, Encoding.Default.GetBytes("404 not found")); } private void Relay(Stream input, Stream output) { // Stream ããèªããªããªãã¾ã§éåä¿¡ã byte[] buffer = new byte[4096]; while (true) { int bytesRead = input.Read(buffer, 0, buffer.Length); if (bytesRead == 0) break; output.Write(buffer, 0, bytesRead); } input.Close(); output.Close(); } private void SendResponse(HttpListenerContext context, int code, string description, bool keepalive, byte[] body) { context.Response.StatusCode = code; context.Response.StatusDescription = description; context.Response.ProtocolVersion = HttpVersion.Version11; context.Response.KeepAlive = keepalive; if (body != null) { context.Response.ContentType = "text/plain"; context.Response.ContentLength64 = body.Length; context.Response.OutputStream.Write(body, 0, body.Length); context.Response.OutputStream.Close(); } else { context.Response.ContentLength64 = 0; } } private void SendFile(HttpListenerContext context, int code, string description, bool keepalive, byte[] body, string contentType) { context.Response.StatusCode = code; context.Response.StatusDescription = description; context.Response.ProtocolVersion = HttpVersion.Version11; context.Response.KeepAlive = keepalive; if (body != null) { context.Response.ContentType = contentType; context.Response.ContentLength64 = body.Length; context.Response.OutputStream.Write(body, 0, body.Length); context.Response.OutputStream.Close(); } else { context.Response.ContentLength64 = 0; } } } }