Перевод статьи на http://www.codeproject.com/Articles/452052/Build-Your-Own-Web-Server
Скачать: [Загрузка не найдена] Скачать: [Загрузка не найдена]
Вступление
Мы будем писать свой простой WebServer, который сможет отправлять ответы на наиболее известные методы HTTP (GET и POST).
HTTP протокол
HTTP протокол связи между сервером и клиентом. Использует в качестве транспорта для отправки и получения протокол TCP/IP.
У HTTP есть несколько методов, но мы реализуем только два из них: это GET и POST.
GET
Что происходит, когда мы нажимаем в адресной строке браузера Enter? (Обычно мы не указываем порт через знак :, хотя для TCP/IP он необходим, так как он задан для HTTP по умолчанию, это порт 80. Если он отличен от 80 то его необходимо указывать через двоеточия)
GET / HTTP/1.1\r\n Host: atasoyweb.net\r\n User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:14.0) Gecko/20100101 Firefox/14.0.1\r\n Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n Accept-Language: tr-tr,tr;q=0.8,en-us;q=0.5,en;q=0.3\r\n Accept-Encoding: gzip, deflate\r\n Connection: keep-alive\r\n\r\n
Это GET запрос, который посылается от нашего браузера к серверу через TCP/IP. Это означает, что браузер запрашивает у сервер содержимое «/» корневой папки «atasoyweb.net».
Мы сами (или браузер) можем добавлять дополнительные заголовки. Но самый упрощенный вариант запроса выглядит так:
GET / HTTP/1.1\r\n Host: atasoyweb.net\r\n\r\n
POST
POST запросы аналогичны запросам GET. В GET запрос, переменные добавляются к URL-адресов используется знак ?. А в POST запрос, переменные добавляются в в тело HTTP-запроса, а размер передаваемых данных в байтах указывается в заголовке Content-Length:
POST /index.html HTTP/1.1\r\n Host: atasoyweb.net\r\n User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:15.0) Gecko/20100101 Firefox/15.0.1\r\n Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n Accept-Language: tr-tr,tr;q=0.8,en-us;q=0.5,en;q=0.3\r\n Accept-Encoding: gzip, deflate\r\n Connection: keep-alive\r\n Referer: http://atasoyweb.net/\r\n Content-Type: application/x-www-form-urlencoded\r\n Content-Length: 35\r\n\r\n variable1=value1&variable2=value2
Упрощенный вариант POST запроса:
POST /index.html HTTP/1.1\r\n Host: atasoyweb.net\r\n Content-Length: 35\r\n\r\n variable1=value1&variable2=value2
Ответы
Когда запрос поступает на сервер он обрабатывается и возвращается ответ с кодом состояния:
HTTP/1.1 200 OK\r\n Server: Apache/1.3.3.7 (Unix) (Red-Hat/Linux)\r\n Content-Length: {content_length}\r\n Connection: close\r\n Content-Type: text/html; charset=UTF-8\r\n\r\n the content of which length is equal to {content_length}
Это заголовок ответа. «200 OK» означает, что все в порядке, запрашиваемый контент, будет возвращен. Есть много кодов состояний, но мы будем использовать только 200, 501 и 404:
- «501 Not Implemented — «Не реализовано 501»: метод не реализован. Мы реализуем только GET и POST. Для всех других методов будем выдавать это состояние
- «404 Not Found»: запрашиваемый контент не найден.
Типы контента
Сервер в своем ответе должен указать тип передаваемого контента. Есть много типов контента, их также называют MIME типы (многоцелевые расширения почты интернета).
Вот типы содержимого, которые мы будем использовать в нашем сервере: (Вы можете изменить код и добавить новые типы):
- text/html
- text/xml
- text/plain
- text/css
- image/png
- image/gif
- image/jpg
- image/jpeg
- application/zip
Если сервер укажет неверный тип контента, то содержимое будет неправильно истолковано. Например, если сервер посылает простой текст с помощью «image/png», то клиент попытается показать текст в виде изображения.
Многопоточность
Если мы хотим, чтобы наш сервер, был доступен нескольким клиентам одновременно, мы должны создавать новые потоки для каждого запроса. Таким образом, каждый поток обрабатывает один запрос и завершается после отдачи контента.
Реализация
Теперь мы готовы к реализации нашего простого вэб сервера. Сперва наперво определим переменные которые будем использовать:
public bool running = false; //Запущено ли? private int timeout = 8; // Лиммт времени на приём данных. private Encoding charEncoder = Encoding.UTF8; // Кодировка private Socket serverSocket; // Нащ сокет private string contentPath; // Корневая папка для контента // Поодерживаемый контент нашим сервером // Вы можете добавить больше // Смотреть здесь: http://www.webmaster-toolkit.com/mime-types.shtml private Dictionary<string, string> extensions = new Dictionary<string, string>() { //{ "extension", "content type" } { "htm", "text/html" }, { "html", "text/html" }, { "xml", "text/xml" }, { "txt", "text/plain" }, { "css", "text/css" }, { "png", "image/png" }, { "gif", "image/gif" }, { "jpg", "image/jpg" }, { "jpeg", "image/jpeg" }, { "zip", "application/zip"} };
Метод Start, запускающий наш сервер
public bool start(IPAddress ipAddress, int port, int maxNOfCon, string contentPath) { if (running) return false; // Если уже запущено, то выходим try { // tcp/ip сокет (ipv4) serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); serverSocket.Bind(new IPEndPoint(ipAddress, port)); serverSocket.Listen(maxNOfCon); serverSocket.ReceiveTimeout = timeout; serverSocket.SendTimeout = timeout; running = true; this.contentPath = contentPath; } catch { return false; } // Наш поток ждет новые подключения и создает новые потоки. Thread requestListenerT = new Thread(() => { while (running) { Socket clientSocket; try { clientSocket = serverSocket.Accept(); // Создаем новый поток для нового клиента и продолжаем слушать сокет. Thread requestHandler = new Thread(() => { clientSocket.ReceiveTimeout = timeout; clientSocket.SendTimeout = timeout; try { handleTheRequest(clientSocket); } catch { try { clientSocket.Close(); } catch { } } }); requestHandler.Start(); } catch{} } }); requestListenerT.Start(); return true; }
Метод Stop, останавливающий сервер
public void stop() { if (running) { running = false; try { serverSocket.Close(); } catch { } serverSocket = null; } }
Наиболее важная часть кода:
private void handleTheRequest(Socket clientSocket) { byte[] buffer = new byte[10240]; // 10 kb, just in case int receivedBCount = clientSocket.Receive(buffer); // Получаем запрос string strReceived = charEncoder.GetString(buffer, 0, receivedBCount); // Парсим запрос string httpMethod = strReceived.Substring(0, strReceived.IndexOf(" ")); int start = strReceived.IndexOf(httpMethod) + httpMethod.Length + 1; int length = strReceived.LastIndexOf("HTTP") - start - 1; string requestedUrl = strReceived.Substring(start, length); string requestedFile; if (httpMethod.Equals("GET") || httpMethod.Equals("POST")) requestedFile = requestedUrl.Split('?')[0]; else // Вы можете реализовать другие методы { notImplemented(clientSocket); return; } requestedFile = requestedFile.Replace("/", "\\").Replace("\\..", ""); // Not to go back start = requestedFile.LastIndexOf('.') + 1; if (start > 0) { length = requestedFile.Length - start; string extension = requestedFile.Substring(start, length); if (extensions.ContainsKey(extension)) // Мы поддерживаем это расширение? if (File.Exists(contentPath + requestedFile)) // Если да // ТО отсылаем запрашиваемы контент: sendOkResponse(clientSocket, File.ReadAllBytes(contentPath + requestedFile), extensions[extension]); else notFound(clientSocket); // Мы не поддерживаем данный контент. } else { // Если файл не указан, пробуем послать index.html // Вы можете добавить больше(например "default.html") if (requestedFile.Substring(length - 1, 1) != "\\") requestedFile += "\\"; if (File.Exists(contentPath + requestedFile + "index.htm")) sendOkResponse(clientSocket, File.ReadAllBytes(contentPath + requestedFile + "\\index.htm"), "text/html"); else if (File.Exists(contentPath + requestedFile + "index.html")) sendOkResponse(clientSocket, File.ReadAllBytes(contentPath + requestedFile + "\\index.html"), "text/html"); else notFound(clientSocket); } }
Ответы на коды статусов:
private void notImplemented(Socket clientSocket) { sendResponse(clientSocket, "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"> </head><body><h2>Atasoy Simple Web Server</h2><div>501 - Method Not Implemented</div></body></html>", "501 Not Implemented", "text/html"); } private void notFound(Socket clientSocket) { sendResponse(clientSocket, "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"></head><body><h2>Atasoy Simple Web Server</h2><div>404 - Not Found</div></body></html>", "404 Not Found", "text/html"); } private void sendOkResponse(Socket clientSocket, byte[] bContent, string contentType) { sendResponse(clientSocket, bContent, "200 OK", contentType); }
Метод, который будет отправлять ответы клиентам:
// For strings private void sendResponse(Socket clientSocket, string strContent, string responseCode, string contentType) { byte[] bContent = charEncoder.GetBytes(strContent); sendResponse(clientSocket, bContent, responseCode, contentType); } // For byte arrays private void sendResponse(Socket clientSocket, byte[] bContent, string responseCode, string contentType) { try { byte[] bHeader = charEncoder.GetBytes( "HTTP/1.1 " + responseCode + "\r\n" + "Server: Atasoy Simple Web Server\r\n" + "Content-Length: " + bContent.Length.ToString() + "\r\n" + "Connection: close\r\n" + "Content-Type: " + contentType + "\r\n\r\n"); clientSocket.Send(bHeader); clientSocket.Send(bContent); clientSocket.Close(); } catch { } }
Как использовать:
// Создаем Server server = new Server(); // Запускаем server.start(ipAddress, port, maxconnections, contentpath); // останавливаем server.stop();
Не работает передача параметров:
?par1=1111&par2=22222