Simple multithreaded HTTP server in C# for static content

Source code (through Thread)

« Back to Nizarium test page



Copy Source | Copy HTML
       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:                  // Принимаем нового клиента
     186:                  TcpClient Client = Listener.AcceptTcpClient();
     187:                  // Создаем поток
     188:                  Thread Thread = new Thread(new ParameterizedThreadStart(ClientThread));
     189:                  // И запускаем этот поток, передавая ему принятого клиента
     190:                  Thread.Start(Client);
     191:              }
     192:          }
     193:   
     194:          static void ClientThread(Object StateInfo)
     195:          {
     196:              // Просто создаем новый экземпляр класса Client и передаем ему приведенный к классу TcpClient объект StateInfo
     197:              new Client((TcpClient)StateInfo);
     198:          }
     199:   
     200:          // Остановка сервера
     201:          ~Server()
     202:          {
     203:              // Если "слушатель" был создан
     204:              if (Listener != null)
     205:              {
     206:                  // Остановим его
     207:                  Listener.Stop();
     208:              }
     209:          }
     210:   
     211:          static void Main(string[] args)
     212:          {
     213:              // Создадим новый сервер на порту 80
     214:              new Server(80);
     215:          }
     216:      }
     217:  }
     218: