├── readme.md ├── AspNetServer ├── SimpleHost.cs ├── Properties │ └── AssemblyInfo.cs ├── Program.cs ├── WebServer.cs ├── RequestInfo.cs ├── AspNetServer.csproj ├── WorkerRequest.cs └── HttpProcessor.cs ├── AspNetServer.sln └── .gitignore /readme.md: -------------------------------------------------------------------------------- 1 | 本程序是用 C# 写的一个简易的 Web 服务器,可以提供 Asp.Net 的运行环境。 2 | -------------------------------------------------------------------------------- /AspNetServer/SimpleHost.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Web; 6 | 7 | namespace AspNetServer 8 | { 9 | public class SimpleHost : MarshalByRefObject 10 | { 11 | public string PhysicalDir { get; private set; } 12 | 13 | public string VituralDir { get; private set; } 14 | 15 | public void Config(string vitrualDir, string physicalDir) 16 | { 17 | VituralDir = vitrualDir; 18 | PhysicalDir = physicalDir; 19 | } 20 | 21 | public void ProcessRequest(HttpProcessor processor, RequestInfo requestInfo) 22 | { 23 | WorkerRequest workerRequest = new WorkerRequest(this, processor, requestInfo); 24 | HttpRuntime.ProcessRequest(workerRequest); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /AspNetServer.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNetServer", "AspNetServer\AspNetServer.csproj", "{8C8C1039-FDC8-4953-A942-8A48D66E8E55}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Any CPU = Debug|Any CPU 9 | Release|Any CPU = Release|Any CPU 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {8C8C1039-FDC8-4953-A942-8A48D66E8E55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 13 | {8C8C1039-FDC8-4953-A942-8A48D66E8E55}.Debug|Any CPU.Build.0 = Debug|Any CPU 14 | {8C8C1039-FDC8-4953-A942-8A48D66E8E55}.Release|Any CPU.ActiveCfg = Release|Any CPU 15 | {8C8C1039-FDC8-4953-A942-8A48D66E8E55}.Release|Any CPU.Build.0 = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /AspNetServer/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // 有关程序集的常规信息通过以下 6 | // 特性集控制。更改这些特性值可修改 7 | // 与程序集关联的信息。 8 | [assembly: AssemblyTitle("AspNetServer")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Microsoft")] 12 | [assembly: AssemblyProduct("AspNetServer")] 13 | [assembly: AssemblyCopyright("Copyright © Microsoft 2013")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // 将 ComVisible 设置为 false 使此程序集中的类型 18 | // 对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型, 19 | // 则将该类型上的 ComVisible 特性设置为 true。 20 | [assembly: ComVisible(false)] 21 | 22 | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID 23 | [assembly: Guid("8801dd0f-72c7-41fe-a375-689e9509b65a")] 24 | 25 | // 程序集的版本信息由下面四个值组成: 26 | // 27 | // 主版本 28 | // 次版本 29 | // 生成号 30 | // 修订号 31 | // 32 | // 可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值, 33 | // 方法是按如下所示使用“*”: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /AspNetServer/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Web.Hosting; 8 | 9 | namespace AspNetServer 10 | { 11 | class Program 12 | { 13 | static void Main(string[] args) 14 | { 15 | int port; 16 | string dir = Directory.GetCurrentDirectory(); 17 | if(args.Length==0 || !int.TryParse(args[0],out port)) 18 | { 19 | port = 45758; 20 | } 21 | 22 | InitHostFile(dir); 23 | SimpleHost host= (SimpleHost) ApplicationHost.CreateApplicationHost(typeof (SimpleHost), "/", dir); 24 | host.Config("/", dir); 25 | 26 | WebServer server = new WebServer(host, port); 27 | server.Start(); 28 | } 29 | 30 | //需要拷贝执行文件 才能创建ASP.NET应用程序域 31 | private static void InitHostFile(string dir) 32 | { 33 | string path = Path.Combine(dir, "bin"); 34 | if (!Directory.Exists(path)) 35 | Directory.CreateDirectory(path); 36 | string source = Assembly.GetExecutingAssembly().Location; 37 | string target = path + "/" + Assembly.GetExecutingAssembly().GetName().Name + ".exe"; 38 | if(File.Exists(target)) 39 | File.Delete(target); 40 | File.Copy(source, target); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /AspNetServer/WebServer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Net.Sockets; 6 | using System.Text; 7 | using System.Threading; 8 | 9 | namespace AspNetServer 10 | { 11 | class WebServer 12 | { 13 | private Socket _serverSocket; 14 | 15 | private SimpleHost _host; 16 | 17 | public int Port { get; private set; } 18 | 19 | public bool IsRuning { get; set; } 20 | 21 | public WebServer(SimpleHost host, int port) 22 | { 23 | _host = host; 24 | Port = port; 25 | } 26 | 27 | public void Start() 28 | { 29 | _serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 30 | _serverSocket.ExclusiveAddressUse = true; 31 | _serverSocket.Bind(new IPEndPoint(IPAddress.Any, Port)); 32 | _serverSocket.Listen(1000); 33 | IsRuning = true; 34 | 35 | Console.WriteLine("Serving HTTP on 0.0.0.0 port " + Port + " ..."); 36 | 37 | new Thread(OnStart).Start(); 38 | } 39 | 40 | private void OnStart(object state) 41 | { 42 | while (IsRuning) 43 | { 44 | try 45 | { 46 | Socket socket = _serverSocket.Accept(); 47 | //AcceptSocket(socket); 48 | ThreadPool.QueueUserWorkItem(AcceptSocket, socket); 49 | } 50 | catch (Exception ex) 51 | { 52 | Console.WriteLine(ex); 53 | Thread.Sleep(100); 54 | } 55 | } 56 | } 57 | 58 | private void AcceptSocket(object state) 59 | { 60 | if (IsRuning) 61 | { 62 | Socket socket = state as Socket; 63 | HttpProcessor processor = new HttpProcessor(_host, socket); 64 | processor.ProcessRequest(); 65 | } 66 | } 67 | 68 | public void Stop() 69 | { 70 | IsRuning = false; 71 | if (_serverSocket != null) 72 | _serverSocket.Close(); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /AspNetServer/RequestInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Specialized; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Text; 7 | using System.Web; 8 | 9 | namespace AspNetServer 10 | { 11 | public class RequestInfo:MarshalByRefObject 12 | { 13 | public string RawUrl { get; private set; } 14 | 15 | public string Protocol { get; private set; } 16 | 17 | public string FilePath { get; private set; } 18 | 19 | public string QueryString { get; private set; } 20 | 21 | public string HttpMethod { get; private set; } 22 | 23 | public NameValueCollection Headers { get; private set; } 24 | 25 | public IPEndPoint RemoteEndPoint { get; set; } 26 | 27 | public IPEndPoint LocalEndPoint { get; set; } 28 | 29 | public string EntityBody { get; private set; } 30 | 31 | private string _rawRequestHeaders; 32 | 33 | private bool _parsed; 34 | 35 | public RequestInfo(string requestHeaders) 36 | { 37 | _rawRequestHeaders = requestHeaders; 38 | } 39 | 40 | public void ParseHeaders() 41 | { 42 | if (!_parsed) 43 | { 44 | DoParse(); 45 | _parsed = true; 46 | } 47 | } 48 | 49 | private void DoParse() 50 | { 51 | string[] lines = _rawRequestHeaders.Split(new[] { "\r\n" }, StringSplitOptions.None); 52 | 53 | string[] actions = lines[0].Split(' '); 54 | HttpMethod = actions[0]; 55 | RawUrl = actions[1]; 56 | Protocol = actions[2]; 57 | 58 | string[] path = RawUrl.Split('?'); 59 | FilePath =HttpUtility.UrlDecode(path[0]); 60 | if (path.Length == 2) 61 | QueryString = path[1]; 62 | 63 | Headers = new NameValueCollection(); 64 | 65 | bool headerComplete = false; 66 | for (int i = 1; i < lines.Length; i++) 67 | { 68 | string line = lines[i]; 69 | if(string.IsNullOrEmpty(line)) 70 | { 71 | headerComplete = true; 72 | continue; 73 | } 74 | 75 | if(headerComplete) 76 | { 77 | EntityBody = line; 78 | } 79 | else 80 | { 81 | int separator = line.IndexOf(":"); 82 | Headers.Add(line.Substring(0, separator), 83 | line.Substring(separator + 1, line.Length - separator - 1).TrimStart()); 84 | } 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | 11 | [Dd]ebug/ 12 | [Rr]elease/ 13 | x64/ 14 | build/ 15 | [Bb]in/ 16 | [Oo]bj/ 17 | 18 | # MSTest test Results 19 | [Tt]est[Rr]esult*/ 20 | [Bb]uild[Ll]og.* 21 | 22 | *_i.c 23 | *_p.c 24 | *.ilk 25 | *.meta 26 | *.obj 27 | *.pch 28 | *.pdb 29 | *.pgc 30 | *.pgd 31 | *.rsp 32 | *.sbr 33 | *.tlb 34 | *.tli 35 | *.tlh 36 | *.tmp 37 | *.tmp_proj 38 | *.log 39 | *.vspscc 40 | *.vssscc 41 | .builds 42 | *.pidb 43 | *.log 44 | *.scc 45 | 46 | # Visual C++ cache files 47 | ipch/ 48 | *.aps 49 | *.ncb 50 | *.opensdf 51 | *.sdf 52 | *.cachefile 53 | 54 | # Visual Studio profiler 55 | *.psess 56 | *.vsp 57 | *.vspx 58 | 59 | # Guidance Automation Toolkit 60 | *.gpState 61 | 62 | # ReSharper is a .NET coding add-in 63 | _ReSharper*/ 64 | *.[Rr]e[Ss]harper 65 | 66 | # TeamCity is a build add-in 67 | _TeamCity* 68 | 69 | # DotCover is a Code Coverage Tool 70 | *.dotCover 71 | 72 | # NCrunch 73 | *.ncrunch* 74 | .*crunch*.local.xml 75 | 76 | # Installshield output folder 77 | [Ee]xpress/ 78 | 79 | # DocProject is a documentation generator add-in 80 | DocProject/buildhelp/ 81 | DocProject/Help/*.HxT 82 | DocProject/Help/*.HxC 83 | DocProject/Help/*.hhc 84 | DocProject/Help/*.hhk 85 | DocProject/Help/*.hhp 86 | DocProject/Help/Html2 87 | DocProject/Help/html 88 | 89 | # Click-Once directory 90 | publish/ 91 | 92 | # Publish Web Output 93 | *.Publish.xml 94 | *.pubxml 95 | 96 | # NuGet Packages Directory 97 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 98 | #packages/ 99 | 100 | # Windows Azure Build Output 101 | csx 102 | *.build.csdef 103 | 104 | # Windows Store app package directory 105 | AppPackages/ 106 | 107 | # Others 108 | sql/ 109 | ClientBin/ 110 | [Ss]tyle[Cc]op.* 111 | ~$* 112 | *~ 113 | *.dbmdl 114 | *.[Pp]ublish.xml 115 | *.pfx 116 | *.publishsettings 117 | 118 | # RIA/Silverlight projects 119 | Generated_Code/ 120 | 121 | # Backup & report files from converting an old project file to a newer 122 | # Visual Studio version. Backup files are not needed, because we have git ;-) 123 | _UpgradeReport_Files/ 124 | Backup*/ 125 | UpgradeLog*.XML 126 | UpgradeLog*.htm 127 | 128 | # SQL Server files 129 | App_Data/*.mdf 130 | App_Data/*.ldf 131 | 132 | # ========================= 133 | # Windows detritus 134 | # ========================= 135 | 136 | # Windows image file caches 137 | Thumbs.db 138 | ehthumbs.db 139 | 140 | # Folder config file 141 | Desktop.ini 142 | 143 | # Recycle Bin used on file shares 144 | $RECYCLE.BIN/ 145 | 146 | # Mac crap 147 | .DS_Store 148 | 149 | -------------------------------------------------------------------------------- /AspNetServer/AspNetServer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {8C8C1039-FDC8-4953-A942-8A48D66E8E55} 8 | Exe 9 | Properties 10 | AspNetServer 11 | AspNetServer 12 | v4.0 13 | 512 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 61 | -------------------------------------------------------------------------------- /AspNetServer/WorkerRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Specialized; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Web; 7 | using System.Web.Hosting; 8 | 9 | namespace AspNetServer 10 | { 11 | class WorkerRequest : HttpWorkerRequest 12 | { 13 | private SimpleHost _host; 14 | 15 | private HttpProcessor _processor; 16 | 17 | private readonly RequestInfo _requestInfo; 18 | 19 | //输出相关 20 | private int _statusCode; 21 | private Dictionary _responseHeaders; 22 | private IList _responseBodyBytes; 23 | 24 | //请求相关 25 | private string[] _knownRequestHeaders; 26 | private string[][] _unknownRequestHeaders; 27 | 28 | private bool _isHeaderSent; 29 | 30 | public WorkerRequest(SimpleHost host, HttpProcessor processor, RequestInfo requestInfo) 31 | { 32 | _host = host; 33 | _processor = processor; 34 | _requestInfo = requestInfo; 35 | 36 | _responseHeaders = new Dictionary(); 37 | _responseBodyBytes = new List(); 38 | 39 | ParseRequestHeaders(); 40 | } 41 | 42 | private void ParseRequestHeaders() 43 | { 44 | _knownRequestHeaders=new string[40]; 45 | NameValueCollection unknownHeaders=new NameValueCollection(); 46 | for (int i = 0; i < _requestInfo.Headers.Count; i++) 47 | { 48 | string name = _requestInfo.Headers.Keys[i]; 49 | string value = _requestInfo.Headers[i]; 50 | int index = HttpWorkerRequest.GetKnownRequestHeaderIndex(name); 51 | if (index >= 0) 52 | _knownRequestHeaders[index] =value; 53 | else 54 | unknownHeaders.Add(name,value); 55 | } 56 | 57 | _unknownRequestHeaders=new string[unknownHeaders.Count][]; 58 | for (int i = 0; i < unknownHeaders.Count; i++) 59 | { 60 | _unknownRequestHeaders[i] = new[] {unknownHeaders.Keys[i], unknownHeaders[i]}; 61 | } 62 | } 63 | 64 | #region vitural method 65 | 66 | public override string GetAppPath() 67 | { 68 | return _host.VituralDir; 69 | } 70 | 71 | public override string GetAppPathTranslated() 72 | { 73 | return _host.PhysicalDir; 74 | } 75 | 76 | public override string GetFilePath() 77 | { 78 | return _requestInfo.FilePath; 79 | } 80 | 81 | public override string GetFilePathTranslated() 82 | { 83 | string path = GetFilePath(); 84 | path = path.Substring(_host.VituralDir.Length); 85 | path = path.Replace('/', '\\'); 86 | return _host.PhysicalDir + path; 87 | } 88 | 89 | public override byte[] GetPreloadedEntityBody() 90 | { 91 | if(_requestInfo.EntityBody==null) 92 | { 93 | return null; 94 | } 95 | 96 | return Encoding.UTF8.GetBytes(_requestInfo.EntityBody); 97 | } 98 | 99 | public override bool IsEntireEntityBodyIsPreloaded() 100 | { 101 | return true; 102 | } 103 | 104 | public override int ReadEntityBody(byte[] buffer, int size) 105 | { 106 | return buffer.Length; 107 | } 108 | 109 | public override string GetKnownRequestHeader(int index) 110 | { 111 | return _knownRequestHeaders[index]; 112 | } 113 | 114 | public override string GetUnknownRequestHeader(string name) 115 | { 116 | foreach (string key in _requestInfo.Headers) 117 | { 118 | if (string.Compare(name, key, StringComparison.OrdinalIgnoreCase) == 0) 119 | return _requestInfo.Headers[key]; 120 | } 121 | return null; 122 | } 123 | 124 | public override string[][] GetUnknownRequestHeaders() 125 | { 126 | return _unknownRequestHeaders; 127 | } 128 | 129 | #endregion 130 | 131 | public override string GetUriPath() 132 | { 133 | return _requestInfo.FilePath; 134 | } 135 | 136 | public override string GetQueryString() 137 | { 138 | return _requestInfo.QueryString; 139 | } 140 | 141 | public override string GetRawUrl() 142 | { 143 | return _requestInfo.RawUrl; 144 | } 145 | 146 | public override string GetHttpVerbName() 147 | { 148 | return _requestInfo.HttpMethod; 149 | } 150 | 151 | public override string GetHttpVersion() 152 | { 153 | return _requestInfo.Protocol; 154 | } 155 | 156 | public override string GetRemoteAddress() 157 | { 158 | return _requestInfo.RemoteEndPoint.Address.ToString(); 159 | } 160 | 161 | public override int GetRemotePort() 162 | { 163 | return _requestInfo.RemoteEndPoint.Port; 164 | } 165 | 166 | public override string GetLocalAddress() 167 | { 168 | return _requestInfo.LocalEndPoint.Address.ToString(); 169 | } 170 | 171 | public override int GetLocalPort() 172 | { 173 | return _requestInfo.LocalEndPoint.Port; 174 | } 175 | 176 | public override void SendStatus(int statusCode, string statusDescription) 177 | { 178 | _statusCode = statusCode; 179 | } 180 | 181 | public override void SendKnownResponseHeader(int index, string value) 182 | { 183 | _responseHeaders[HttpWorkerRequest.GetKnownResponseHeaderName(index)] = value; 184 | } 185 | 186 | public override void SendUnknownResponseHeader(string name, string value) 187 | { 188 | _responseHeaders[name] = value; 189 | } 190 | 191 | public override void SendResponseFromMemory(byte[] data, int length) 192 | { 193 | if (length > 0) 194 | { 195 | byte[] dst = new byte[length]; 196 | Buffer.BlockCopy(data, 0, dst, 0, length); 197 | _responseBodyBytes.Add(dst); 198 | } 199 | } 200 | 201 | public override void SendResponseFromFile(string filename, long offset, long length) 202 | { 203 | } 204 | 205 | public override void SendResponseFromFile(IntPtr handle, long offset, long length) 206 | { 207 | } 208 | 209 | public override void FlushResponse(bool finalFlush) 210 | { 211 | if (!_isHeaderSent) 212 | _processor.SendHeaders(_statusCode, _responseHeaders, -1, finalFlush); 213 | 214 | for (int i = 0; i < _responseBodyBytes.Count; i++) 215 | { 216 | byte[] data = _responseBodyBytes[i]; 217 | _processor.SendResponse(data); 218 | } 219 | 220 | _responseBodyBytes = new List(); 221 | if (finalFlush) 222 | _processor.Close(); 223 | } 224 | 225 | public override void EndOfRequest() 226 | { 227 | } 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /AspNetServer/HttpProcessor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Specialized; 4 | using System.Globalization; 5 | using System.IO; 6 | using System.Net; 7 | using System.Net.Sockets; 8 | using System.Text; 9 | using System.Web; 10 | 11 | namespace AspNetServer 12 | { 13 | public class HttpProcessor : MarshalByRefObject 14 | { 15 | private Socket _socket; 16 | 17 | private bool _isClosed; 18 | 19 | private SimpleHost _host; 20 | 21 | private static readonly Dictionary staticFileContentType = new Dictionary() 22 | { 23 | {"htm", "text/html"}, 24 | {"html", "text/html"}, 25 | {"xml", "text/xml"}, 26 | {"txt", "text/plain"}, 27 | {"css", "text/css"}, 28 | {"js", "application/x-javascript"}, 29 | {"png", "image/png"}, 30 | {"gif", "image/gif"}, 31 | {"jpg", "image/jpg"}, 32 | {"jpeg", "image/jpeg"}, 33 | {"zip", "application/zip"} 34 | }; 35 | 36 | public HttpProcessor(SimpleHost host, Socket socket) 37 | { 38 | _host = host; 39 | _socket = socket; 40 | } 41 | 42 | public void ProcessRequest() 43 | { 44 | try 45 | { 46 | RequestInfo requestInfo = ParseRequest(); 47 | if (requestInfo != null) 48 | { 49 | string staticContentType = GetStaticContentType(requestInfo); 50 | if (!string.IsNullOrEmpty(staticContentType)) 51 | { 52 | WriteFileResponse(requestInfo.FilePath, staticContentType); 53 | } 54 | else if (requestInfo.FilePath.EndsWith("/")) 55 | { 56 | WriteDirResponse(requestInfo.FilePath); 57 | } 58 | else 59 | { 60 | _host.ProcessRequest(this, requestInfo); 61 | //WorkerRequest workerRequest = new WorkerRequest(this, requestInfo); 62 | //HttpRuntime.ProcessRequest(workerRequest); 63 | } 64 | } 65 | else 66 | { 67 | SendErrorResponse(400); 68 | } 69 | } 70 | finally 71 | { 72 | Close();//确保连接关闭 73 | } 74 | } 75 | 76 | private string GetStaticContentType(RequestInfo requestInfo) 77 | { 78 | int dotStart = requestInfo.FilePath.LastIndexOf('.') + 1; 79 | string contentType = string.Empty; 80 | if (dotStart > 0) 81 | { 82 | string extension = requestInfo.FilePath.Substring(dotStart, requestInfo.FilePath.Length - dotStart); 83 | foreach (KeyValuePair fileType in staticFileContentType) 84 | { 85 | if (extension.ToLower() == fileType.Key) 86 | { 87 | contentType = fileType.Value; 88 | break; 89 | } 90 | } 91 | } 92 | return contentType; 93 | } 94 | 95 | private void WriteFileResponse(string filePath, string contentType) 96 | { 97 | string fullPath = Path.Combine(_host.PhysicalDir, filePath.TrimStart('/')); 98 | if (!File.Exists(fullPath)) 99 | SendErrorResponse(404); 100 | else 101 | { 102 | try 103 | { 104 | using (FileStream fs = new FileStream(fullPath, FileMode.Open, FileAccess.Read)) 105 | { 106 | int length = (int)fs.Length; 107 | byte[] buffer = new byte[length]; 108 | int contentLength = fs.Read(buffer, 0, length); 109 | string headers = BuildHeader(200, 110 | new Dictionary() { { "Content-Type", contentType } }, 111 | contentLength, false); 112 | _socket.Send(Encoding.UTF8.GetBytes(headers)); 113 | _socket.Send(buffer, 0, contentLength, SocketFlags.None); 114 | } 115 | } 116 | finally 117 | { 118 | Close(); 119 | } 120 | } 121 | } 122 | 123 | private void WriteDirResponse(string filePath) 124 | { 125 | string dir = Path.Combine(_host.PhysicalDir, filePath.TrimStart('/')); 126 | if (!Directory.Exists(dir)) 127 | SendErrorResponse(404); 128 | else 129 | { 130 | string[] files = Directory.GetFileSystemEntries(dir); 131 | StringBuilder builder = new StringBuilder(files.Length + 2); 132 | builder.Append("
    "); 133 | foreach (string file in files) 134 | { 135 | string filename = Path.GetFileName(file); 136 | bool isDir = Directory.Exists(file); 137 | builder.AppendFormat("
  1. {1}{2}
  2. ", isDir ? filename + "/" : filename, 138 | filename, isDir ? "↓" : ""); 139 | } 140 | builder.Append("
"); 141 | SendResponse(200, builder.ToString(), new Dictionary() { { "Content-Type", "text/html" } }); 142 | } 143 | } 144 | 145 | private RequestInfo ParseRequest() 146 | { 147 | try 148 | { 149 | string requestHeaders = GetRequestHeaders(); 150 | if (!string.IsNullOrEmpty(requestHeaders)) 151 | { 152 | Console.WriteLine(requestHeaders); 153 | 154 | RequestInfo requestInfo = new RequestInfo(requestHeaders); 155 | requestInfo.ParseHeaders(); 156 | IPEndPoint remoteEndPoint = (IPEndPoint)_socket.RemoteEndPoint; 157 | requestInfo.RemoteEndPoint = remoteEndPoint; 158 | IPEndPoint localEndPoint = (IPEndPoint)_socket.LocalEndPoint; 159 | requestInfo.LocalEndPoint = localEndPoint; 160 | 161 | return requestInfo; 162 | } 163 | } 164 | catch (Exception ex) 165 | { 166 | Console.WriteLine(ex); 167 | } 168 | 169 | return null; 170 | } 171 | 172 | private string GetRequestHeaders() 173 | { 174 | int bufferSize = 0x8000; 175 | byte[] receiveBytes = new byte[0]; 176 | while (true) 177 | { 178 | byte[] bytes = ReceiveBytes(bufferSize); 179 | if (bytes == null || bytes.Length == 0) 180 | break; 181 | if (receiveBytes.Length == 0) 182 | receiveBytes = bytes; 183 | else 184 | { 185 | int receiveBytesLength = receiveBytes.Length + bytes.Length; 186 | byte[] dst = new byte[receiveBytesLength]; 187 | Buffer.BlockCopy(receiveBytes, 0, dst, 0, receiveBytes.Length); 188 | Buffer.BlockCopy(bytes, 0, dst, receiveBytes.Length, bytes.Length); 189 | } 190 | } 191 | 192 | string requestHeaders = Encoding.UTF8.GetString(receiveBytes); 193 | return requestHeaders; 194 | } 195 | 196 | private byte[] ReceiveBytes(int length) 197 | { 198 | int available = GetRequestAvailable(); 199 | available = available > length ? length : available; 200 | byte[] buffer = null; 201 | if (available > 0) 202 | { 203 | buffer = new byte[available]; 204 | int count = _socket.Receive(buffer, available, SocketFlags.None); 205 | if (count < available) 206 | { 207 | byte[] dst = new byte[count]; 208 | if (count > 0) 209 | Buffer.BlockCopy(buffer, 0, dst, 0, count); 210 | buffer = dst; 211 | } 212 | } 213 | 214 | return buffer; 215 | } 216 | 217 | public int GetRequestAvailable() 218 | { 219 | int available = 0; 220 | try 221 | { 222 | if (_socket.Available == 0) 223 | { 224 | _socket.Poll(10000, SelectMode.SelectRead); 225 | if (_socket.Available == 0 && _socket.Connected) 226 | _socket.Poll(10000, SelectMode.SelectRead); 227 | } 228 | 229 | available = _socket.Available; 230 | } 231 | catch 232 | { 233 | //ignore 234 | } 235 | 236 | return available; 237 | } 238 | 239 | private void SendErrorResponse(int statusCode) 240 | { 241 | SendResponse(statusCode, statusCode.ToString()); 242 | } 243 | 244 | public void SendResponse(int statusCode, string body, Dictionary headers = null, bool keepAlive = false) 245 | { 246 | SendResponse(statusCode, Encoding.UTF8.GetBytes(body), headers, keepAlive); 247 | } 248 | 249 | public void SendResponse(int statusCode, byte[] responseBodyBytes, Dictionary headers = null, bool keepAlive = false) 250 | { 251 | SendHeaders(statusCode, headers, responseBodyBytes.Length, keepAlive); 252 | _socket.Send(responseBodyBytes); 253 | 254 | if (!keepAlive) 255 | Close(); 256 | } 257 | 258 | public void SendResponse(byte[] data) 259 | { 260 | _socket.Send(data); 261 | } 262 | 263 | public void SendHeaders(int statusCode, Dictionary headers, int contentLength, bool keepAlive) 264 | { 265 | string header = BuildHeader(statusCode, headers, contentLength, keepAlive); 266 | _socket.Send(Encoding.UTF8.GetBytes(header)); 267 | } 268 | 269 | private string BuildHeader(int statusCode, Dictionary headers, int contentLength, bool keepAlive) 270 | { 271 | StringBuilder builder = new StringBuilder(); 272 | builder.AppendFormat("HTTP/1.1 {0} {1}\r\n", statusCode, HttpWorkerRequest.GetStatusDescription(statusCode)); 273 | builder.AppendFormat("Server: AspNet Simple Server/1.0\r\n"); 274 | builder.AppendFormat("Date: {0}\r\n", 275 | DateTime.Now.ToUniversalTime().ToString("R", DateTimeFormatInfo.InvariantInfo)); 276 | if (contentLength > 0) 277 | builder.AppendFormat("Content-Length: {0}\r\n", contentLength); 278 | if (keepAlive) 279 | builder.Append("Connection: keep-alive\r\n"); 280 | if (headers != null) 281 | { 282 | foreach (KeyValuePair pair in headers) 283 | { 284 | builder.AppendFormat("{0}: {1}\r\n", pair.Key, pair.Value); 285 | } 286 | } 287 | builder.Append("\r\n"); 288 | 289 | return builder.ToString(); 290 | } 291 | 292 | public void Close() 293 | { 294 | try 295 | { 296 | if (!_isClosed) 297 | { 298 | _socket.Shutdown(SocketShutdown.Both); 299 | _socket.Close(); 300 | 301 | _isClosed = true; 302 | } 303 | } 304 | catch 305 | { 306 | //ignore 307 | } 308 | } 309 | } 310 | } --------------------------------------------------------------------------------