1:
2: using System;
3: using System.Collections.Generic;
4: using System.Text;
5: using System.Net.Sockets;
6: using System.Net;
7: using System.Threading;
8: using System.Text.RegularExpressions;
9: using System.IO;
10:
11: namespace HTTPServer
12: {
13: // Класс-обработчик клиента
14: class Client
15: {
16: // Отправка страницы с ошибкой
17: private void SendError(TcpClient Client, int Code)
18: {
19: // Получаем строку вида "200 OK"
20: // HttpStatusCode хранит в себе все статус-коды HTTP/1.1
21: string CodeStr = Code.ToString() + " " + ((HttpStatusCode)Code).ToString();
22: // Код простой HTML-странички
23: string Html = "<html><body><h1>" + CodeStr + "</h1></body></html>";
24: // Необходимые заголовки: ответ сервера, тип и длина содержимого. После двух пустых строк - само содержимое
25: string Str = "HTTP/1.1 " + CodeStr + "\nContent-type: text/html\nContent-Length:" + Html.Length.ToString() + "\n\n" + Html;
26: // Приведем строку к виду массива байт
27: byte[] Buffer = Encoding.ASCII.GetBytes(Str);
28: // Отправим его клиенту
29: Client.GetStream().Write(Buffer, 0, Buffer.Length);
30: // Закроем соединение
31: Client.Close();
32: }
33:
34: // Конструктор класса. Ему нужно передавать принятого клиента от TcpListener
35: public Client(TcpClient Client)
36: {
37: // Объявим строку, в которой будет хранится запрос клиента
38: string Request = "";
39: // Буфер для хранения принятых от клиента данных
40: byte[] Buffer = new byte[1024];
41: // Переменная для хранения количества байт, принятых от клиента
42: int Count;
43: // Читаем из потока клиента до тех пор, пока от него поступают данные
44: while ((Count = Client.GetStream().Read(Buffer, 0, Buffer.Length)) > 0)
45: {
46: // Преобразуем эти данные в строку и добавим ее к переменной Request
47: Request += Encoding.ASCII.GetString(Buffer, 0, Count);
48: // Запрос должен обрываться последовательностью \r\n\r\n
49: // Либо обрываем прием данных сами, если длина строки Request превышает 4 килобайта
50: // Нам не нужно получать данные из POST-запроса (и т. п.), а обычный запрос
51: // по идее не должен быть больше 4 килобайт
52: if (Request.IndexOf("\r\n\r\n") >= 0 || Request.Length > 4096)
53: {
54: break;
55: }
56: }
57:
58: // Парсим строку запроса с использованием регулярных выражений
59: // При этом отсекаем все переменные GET-запроса
60: Match ReqMatch = Regex.Match(Request, @"^\w+\s+([^\s\?]+)[^\s]*\s+HTTP/.*|");
61:
62: // Если запрос не удался
63: if (ReqMatch == Match.Empty)
64: {
65: // Передаем клиенту ошибку 400 - неверный запрос
66: SendError(Client, 400);
67: return;
68: }
69:
70: // Получаем строку запроса
71: string RequestUri = ReqMatch.Groups[1].Value;
72:
73: // Приводим ее к изначальному виду, преобразуя экранированные символы
74: // Например, "%20" -> " "
75: RequestUri = Uri.UnescapeDataString(RequestUri);
76:
77: // Если в строке содержится двоеточие, передадим ошибку 400
78: // Это нужно для защиты от URL типа http://example.com/../../file.txt
79: if (RequestUri.IndexOf("..") >= 0)
80: {
81: SendError(Client, 400);
82: return;
83: }
84:
85: // Если строка запроса оканчивается на "/", то добавим к ней index.html
86: if (RequestUri.EndsWith("/"))
87: {
88: RequestUri += "index.html";
89: }
90:
91: string FilePath = "www/" + RequestUri;
92:
93: // Если в папке www не существует данного файла, посылаем ошибку 404
94: if (!File.Exists(FilePath))
95: {
96: SendError(Client, 404);
97: return;
98: }
99:
100: // Получаем расширение файла из строки запроса
101: string Extension = RequestUri.Substring(RequestUri.LastIndexOf('.'));
102:
103: // Тип содержимого
104: string ContentType = "";
105:
106: // Пытаемся определить тип содержимого по расширению файла
107: switch (Extension)
108: {
109: case ".htm":
110: case ".html":
111: ContentType = "text/html";
112: break;
113: case ".css":
114: ContentType = "text/stylesheet";
115: break;
116: case ".js":
117: ContentType = "text/javascript";
118: break;
119: case ".jpg":
120: ContentType = "image/jpeg";
121: break;
122: case ".jpeg":
123: case ".png":
124: case ".gif":
125: ContentType = "image/" + Extension.Substring(1);
126: break;
127: default:
128: if (Extension.Length > 1)
129: {
130: ContentType = "application/" + Extension.Substring(1);
131: }
132: else
133: {
134: ContentType = "application/unknown";
135: }
136: break;
137: }
138:
139: // Открываем файл, страхуясь на случай ошибки
140: FileStream FS;
141: try
142: {
143: FS = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
144: }
145: catch (Exception)
146: {
147: // Если случилась ошибка, посылаем клиенту ошибку 500
148: SendError(Client, 500);
149: return;
150: }
151:
152: // Посылаем заголовки
153: string Headers = "HTTP/1.1 200 OK\nContent-Type: " + ContentType + "\nContent-Length: " + FS.Length + "\n\n";
154: byte[] HeadersBuffer = Encoding.ASCII.GetBytes(Headers);
155: Client.GetStream().Write(HeadersBuffer, 0, HeadersBuffer.Length);
156:
157: // Пока не достигнут конец файла
158: while (FS.Position < FS.Length)
159: {
160: // Читаем данные из файла
161: Count = FS.Read(Buffer, 0, Buffer.Length);
162: // И передаем их клиенту
163: Client.GetStream().Write(Buffer, 0, Count);
164: }
165:
166: // Закроем файл и соединение
167: FS.Close();
168: Client.Close();
169: }
170: }
171:
172: class Server
173: {
174: TcpListener Listener; // Объект, принимающий TCP-клиентов
175:
176: // Запуск сервера
177: public Server(int Port)
178: {
179: Listener = new TcpListener(IPAddress.Any, Port); // Создаем "слушателя" для указанного порта
180: Listener.Start(); // Запускаем его
181:
182: // В бесконечном цикле
183: while (true)
184: {
185: // Принимаем новых клиентов. После того, как клиент был принят, он передается в новый поток (ClientThread)
186: // с использованием пула потоков.
187: ThreadPool.QueueUserWorkItem(new WaitCallback(ClientThread), Listener.AcceptTcpClient());
188: }
189: }
190:
191: static void ClientThread(Object StateInfo)
192: {
193: // Просто создаем новый экземпляр класса Client и передаем ему приведенный к классу TcpClient объект StateInfo
194: new Client((TcpClient)StateInfo);
195: }
196:
197: // Остановка сервера
198: ~Server()
199: {
200: // Если "слушатель" был создан
201: if (Listener != null)
202: {
203: // Остановим его
204: Listener.Stop();
205: }
206: }
207:
208: static void Main(string[] args)
209: {
210: // Определим нужное максимальное количество потоков
211: // Пусть будет по 4 на каждый процессор
212: int MaxThreadsCount = Environment.ProcessorCount * 4;
213: // Установим максимальное количество рабочих потоков
214: ThreadPool.SetMaxThreads(MaxThreadsCount, MaxThreadsCount);
215: // Установим минимальное количество рабочих потоков
216: ThreadPool.SetMinThreads(2, 2);
217: // Создадим новый сервер на порту 80
218: new Server(80);
219: }
220: }
221: }
222: