├── 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}{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 | }
--------------------------------------------------------------------------------