[伝知ポッドキャスト学習日記]手書きWebサーバ

40673 ワード

2日かけてやったのはASPを明らかにするためだ.Netの動作原理.
このパクリサーバーのインタフェースは簡単で、3つのテキストボックス、IP、ポート、そしてメッセージを表示します.接続ボタン.フォームですか...Form 1と呼びましょう.コードが冗長...ステップ1:
 1 //     ,      , :
2 public Form1()
3 {
4 Control.CheckForIllegalCrossThreadCalls = false;
5 InitializeComponent();
6 }
7 // th , ,
8 private void Form1_FormClosing(object sender, FormClosingEventArgs e)
9 {
10 if (th != null)
11 {
12 th.Abort();
13 }
14 }
15 // ShowMsg :
16 void ShowMsg(string msg)
17 {
18 txtLog.Text += msg + "\r
";
19 }
ステップ2:スレッド内でループリスニングを行い、あまり説明しません.それともSocketのセットですか.
 1 Thread th;
2 private void btnStart_Click(object sender, EventArgs e)
3 {
4 IPAddress ip = IPAddress.Parse(txtIp.Text);
5 IPEndPoint endpoint = new IPEndPoint(ip, int.Parse(txtPort.Text));
6 Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
7
8 try
9 {
10 socket.Bind(endpoint);
11 }
12 catch (Exception ex)
13 {
14 ShowMsg(ex.Message);
15 return;
16 }
17 socket.Listen(10);
18 ShowMsg(" .....");
19
20 btnStart.Enabled = false;
21
22 th = new Thread(Listen);
23 th.IsBackground = true;
24 th.Start(socket);
25 }
26 //
27 void Listen(object o)
28 {
29 Socket socket = o as Socket;
30 while (true)
31 {
32 Socket connect = socket.Accept();
33 DataConnection conn = new DataConnection(connect, ShowMsg);
34 }
35 }
ステップ3:情報の転送が完了するたびに接続が切断されるため、クライアント要求をループで受け入れる必要はありません.上にDataConnectionというクラスがあることに気づきました.このクラスは私たちがカスタマイズしたもので、コードが肥大化しないようにしています.そのコンストラクション関数はDataConnection(Socket conn,DelShowMsg del)で、最初のパラメータは私たちが傍受用のSocketを通じて生成した伝送を担当するSocketで、2番目のパラメータは依頼で、メッセージ表示を行います.
 1 //
2 public delegate void DelShowMsg(string msg);
3 //
4 class DataConnection
5 {
6 //
7 private DelShowMsg del;
8 // socket
9 private Socket connection;
10
11 // Socket
12 public DataConnection(Socket conn,DelShowMsg del)
13 {
14 this.connection = conn;
15 this.del = del;
16
17 //
18 //
19 string msg = RecMsg();
20
21 //
22 Request req = new Request(msg);
23
24 // , ,
25 //
26 Judge(req.Path);
27 }
(クラスのメソッドはまだ書き終わっていません)第4ステップ:第3ステップでは、RecMsgメソッド、Judgeメソッド、Requestクラスが書かれていないことを残します.まずRecMsgメソッドを書きます.
 1 //RecMsg     ,      
2 string RecMsg()
3 {
4 //
5 byte[] buffer = new byte[1024 * 1024 * 5];
6 //
7 int length = connection.Receive(buffer);
8
9 //
10 string msg = System.Text.Encoding.UTF8.GetString(buffer, 0, length);
11 //
12 del(msg);
13
14 del(" ");
15 return msg;
16 }
ステップ5:ステップ3のRequestクラスは、要求メッセージが要求を取得するパスを解析するために使用されます.原理は文字列を切断することです.
 1 class Request
2 {
3 public Request(string msg)
4 {
5 //
6 string[] arrLines = msg.Split(new string[] { "\r
" }, StringSplitOptions.RemoveEmptyEntries);
7 //
8 string[] firstLine = arrLines[0].Split(' ');
9 //
10 method = firstLine[0];
11 path = firstLine[1];
12 protocol = firstLine[2];
13 }
14
15 //
16 private string method;
17 public string Method
18 {
19 get { return method; }
20 set { method = value; }
21 }
22
23 private string path;
24 public string Path
25 {
26 get { return path; }
27 set { path = value; }
28 }
29
30 private string protocol;
31 public string Protocol
32 {
33 get { return protocol; }
34 set { protocol = value; }
35 }
36 }
ステップ6:次に、前のRequest req=new Request(msg)を受け入れるDataConnectionクラスに残されたJudgeメソッドです.その後得られたオブジェクトのpath属性,すなわちアドレスは,ファイルタイプの判断を行う.すなわち、Judge(req.Path);.
 1 void Judge(string path)
2 {
3 //
4 string ext = Path.GetExtension(path);
5 //
6 //
7 switch (ext)
8 {
9 case ".gif":
10 case ".jpg":
11 case ".png":
12 case ".html":
13 case ".htm":
14 case ".css":
15 case ".js":
16 ProcessStaticPage(path);
17 break;
18 case ".aspx":
19 case ".jsp":
20 ProcessDyPage(path);
21 break;
22 default:
23 break;
24 }
25 }
ステップ7:ProcessStaticPageとProcessDyPageは、静的および動的ページを処理する2つの方法であり、サーバを介して応答ヘッダと応答体を返します.応答体は、bufferを定義すればいいと言います.問題は、応答ヘッダが複雑であることです.Responseクラスを定義して生成し、それを取得し、ProcessStaticPageとProcessDyPageの2つの関数を処理する必要があります.このステップはResponseクラスを書くことです.
 1 class Response
2 {
3 //200 , ,
4 private int status = 200;
5 private string contentType;
6 private int contentLength;
7
8 //
9 private Dictionary<int, string> dic;
10
11 //
12 void FillDic()
13 {
14 dic = new Dictionary<int, string>();
15 dic.Add(200,"OK");
16 dic.Add(404, "Object Not Found");
17 dic.Add(302, "Found");
18 }
19
20 // (200 )
21 public Response(string ext, int contentLength)
22 {
23 this.contentLength = contentLength;
24 this.contentType = GetContentType(ext);
25 FillDic();
26 }
27
28 // 200 , 404
29 public Response(int status,string ext,int contentLength)
30 :this(ext,contentLength)
31 {
32 this.status = status;
33 }
34
35 // contentType
36 // contenttype
37 private string GetContentType(string ext)
38 {
39 string contentType = "";
40 switch (ext)
41 {
42 case ".htm":
43 case ".html":
44 contentType = "text/html";
45 break;
46 case ".css":
47 contentType = "text/css";
48 break;
49 case ".js":
50 contentType = "text/javascript";
51 break;
52 case ".jpg":
53 contentType = "image/jpeg";
54 break;
55 case ".gif":
56 contentType = "image/gif";
57 break;
58 default:
59 contentType = "text/html";
60 break;
61 }
62 return contentType;
63 }
64
65 //
66 //
67 public byte[] GetHeaders()
68 {
69 StringBuilder sb = new StringBuilder();
70 sb.Append("HTTP/1.1 "+status+" " + dic[status] + "\r
");
71 sb.Append("Content-Length: " + contentLength+"\r
");
72 // , ,
73 sb.Append("Content-Type: " + contentType + ";charset=utf-8\r
\r
");
74
75 byte[] buffer = System.Text.Encoding.UTF8.GetBytes(sb.ToString());
76 return buffer;
77 }
78 }
ステップ8:静的および動的ページを処理するには、ProcessStaticPageとProcessDyPageの2つの方法を書く必要があります.まず静的にします.
 1 void ProcessStaticPage(string path)
2 {
3 //
4 path =AppDomain.CurrentDomain.BaseDirectory + path.Remove(0,1);
5 //
6 Response res = null;
7 //
8 byte[] buffer;
9
10 //
11 if (!File.Exists(path))
12 {
13 // , 404.html
14 path = AppDomain.CurrentDomain.BaseDirectory + "404.html";
15 // 404
16 using(FileStream fs = new FileStream(path,FileMode.Open))
17 {
18 buffer = new byte[fs.Length];
19 fs.Read(buffer, 0, buffer.Length);
20 res = new Response(404,Path.GetExtension(path), buffer.Length);
21 }
22 }
23 else
24 {
25 //
26 using (FileStream fs = new FileStream(path, FileMode.Open))
27 {
28 buffer = new byte[fs.Length];
29 fs.Read(buffer, 0, buffer.Length);
30 res = new Response(Path.GetExtension(path), buffer.Length);
31 }
32 }
33 //
34 connection.Send(res.GetHeaders());
35 //
36 connection.Send(buffer);
37 //
38 connection.Close();
39 }
ステップ9:ダイナミックページの処理は面倒です.要求されたファイル名に基づいて同じ名前のクラスを探すので、反射技術を使用します.
 1 void ProcessDyPage(string path)
2 {
3 //
4 //
5 string fileName = Path.GetFileNameWithoutExtension(path);
6 //
7 string nameSpace = System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Namespace;
8 //
9 string fullName = nameSpace + "." + fileName;
10
11 // ,IHttpHandler ,
12 // http
13 IHttpHandler hander = Assembly.GetExecutingAssembly().CreateInstance(fullName,true) as IHttpHandler;
14
15 if (hander != null)
16 {
17 // ProcessRequest
18 byte[] buffer = hander.ProcessRequest();
19 Response response = new Response(Path.GetExtension(path), buffer.Length);
20
21 connection.Send(response.GetHeaders());
22 connection.Send(buffer);
23
24 connection.Close();
25 }
26 else
27 {
28 // 404,
29 }
30 }
ステップ10:上にIHttpHandlerインタフェースがあり、インタフェースの中にbyte[]ProcessRequest()があります.方法は、ここでは反射で文字列に対応する同名のクラス名を探しているので、ここで判断ロジックや簡単な工場を書かないのは、判断するとページを増やすたびにコードを変更するので、反射メカニズムを採用します.インタフェースは動的ページに対応するクラスで実現され,中にはhtmlコードがつづられている.例:
 1 class MyPage : IHttpHandler
2 {
3 public byte[] ProcessRequest()
4 {
5 StringBuilder sb = "<html><body>";
6 sb.Append("" + DateTime.Now.ToString());
7 sb.Append("</body></html>");
8 string html = sb.ToString();
9 return Encoding.UTF8.GetBytes(html);
10 }
11 }