#Gromov
Мастер
- Регистрация
- 17 Окт 2014
- Сообщения
- 2,156
- Лучшие ответы
- 0
- Репутация
- 595
Введение
В этой статье рассматривается простой класс HTTP-сервера, который можно добавить в собственный проект, а так же дающий больше представления о протоколе HTTP. Для высоко-производительных сервисов обычно используются устойчивые устойчивые веб-сервера, такие как IIS, Apache или Tomcat. Но HTML настолько гибкий язык интерфейса, что бывает полезен практически в любом приложении или внутреннем (бэкэнд) сервере, где временнЫе затраты на конфигурацию и использование внешнего веб-сервера невыгодны. Всё что нужно для этого - простой HTTP-класс для обработки входящих веб-запросов, который можно легко встроить в приложение.
Использование кода
Сперва рассмотрим, как этот класс можно использовать, а затем разберёмся как он работает и покопаемся в некоторых его особенностях. Для начала унаследуем свой класс от HttpServer и реализуем два абстрактных метода handleGETRequest и handlePOSTRequest...
PHP:
public class MyHttpServer : HttpServer {
public MyHttpServer(int port)
: base(port) {
}
public override void handleGETRequest(HttpProcessor p) {
Console.WriteLine("request: {0}", p.http_url);
p.writeSuccess();
p.outputStream.WriteLine("<html><body><h1>test server</h1>");
p.outputStream.WriteLine("Current Time: " + DateTime.Now.ToString());
p.outputStream.WriteLine("url : {0}", p.http_url);
p.outputStream.WriteLine("<form method=post action=/form>");
p.outputStream.WriteLine("<input type=text name=foo value=foovalue>");
p.outputStream.WriteLine("<input type=submit name=bar value=barvalue>");
p.outputStream.WriteLine("</form>");
}
public override void handlePOSTRequest(HttpProcessor p, StreamReader inputData) {
Console.WriteLine("POST request: {0}", p.http_url);
string data = inputData.ReadToEnd();
p.outputStream.WriteLine("<html><body><h1>test server</h1>");
p.outputStream.WriteLine("<a href=/test>return</a><p>");
p.outputStream.WriteLine("postbody: <pre>{0}</pre>", data);
}
}
PHP:
HttpServer httpServer = new MyHttpServer(8080);
Thread thread = new Thread(new ThreadStart(httpServer.listen));
thread.Start();
Чтобы видеть скрытое содержание Зарегистрируйтесь на форуме!
и увидеть простую html-страничку, сгенерированную нашим сервером. Теперь можно вкратце рассмотреть, что же происходит внутри.Данный веб-сервер разбивается на две составляющие. Класс HttpServer открывает TcpListener на входящем порту и в цикле обрабатывает входящие TCP-запросы, используя AcceptTcpClient(). Это первый этап обработки входящих TCP-соединений. Входящий запрос поступает на наш порт и аксептящий процесс создает новую пару портов для сервера, по которой начнётся общение с клиентом. Эта новая пара портов и есть наша TcpClient-сессия. Такая операция позволяет освободить основной порт, на котором сервер продолжит принимать новые входящие подключения. Как видно из нижеприведённого кода, листенер каждый раз возвращает новый TcpClient, HttpServer создает новый HttpProcessor и запускается новый поток для его обработки. Этот класс также содержит абстрактные методы, которые наш унаследованный класс должен иметь, чтобы генерировать ответ.
PHP:
public abstract class HttpServer {
protected int port;
TcpListener listener;
bool is_active = true;
public HttpServer(int port) {
this.port = port;
}
public void listen() {
listener = new TcpListener(port);
listener.Start();
while (is_active) {
TcpClient s = listener.AcceptTcpClient();
HttpProcessor processor = new HttpProcessor(s, this);
Thread thread = new Thread(new ThreadStart(processor.process));
thread.Start();
Thread.Sleep(1);
}
}
public abstract void handleGETRequest(HttpProcessor p);
public abstract void handlePOSTRequest(HttpProcessor p, StreamReader inputData);
}
PHP:
GET /myurl HTTP/1.0
PHP:
public void parseRequest() {
String request = inputStream.ReadLine();
string[] tokens = request.Split(' ');
if (tokens.Length != 3) {
throw new Exception("invalid http request line");
}
http_method = tokens[0].ToUpper();
http_url = tokens[1];
http_protocol_versionstring = tokens[2];
Console.WriteLine("starting: " + request);
}
PHP:
public void readHeaders() {
Console.WriteLine("readHeaders()");
String line;
while ((line = inputStream.ReadLine()) != null) {
if (line.Equals("")) {
Console.WriteLine("got headers");
return;
}
int separator = line.IndexOf(':');
if (separator == -1) {
throw new Exception("invalid http header line: " + line);
}
String name = line.Substring(0, separator);
int pos = separator + 1;
while ((pos < line.Length) && (line[pos] == ' ')) {
pos++; // strip any spaces
}
string value = line.Substring(pos, line.Length - pos);
Console.WriteLine("header: {0}:{1}",name,value);
httpHeaders[name] = value;
}
}
На данный момент этого достаточно, чтобы обрабатывать простые запросы GET и POST, поэтому мы передаём управление следующему обработчику. В случае POST-запроса есть некоторые тонкости, которые необходимо знать, чтобы принять данные. Один из заголовков запроса содержит длинну передаваемых данных (content-length). Чтобы наш handlePOSTRequest мог обрабатывать данные присланные методом POST, ему необходимо разрешить запрашивать количество байт (указанное в content-length) из потока, чтобы они не застряли во входном потоке. В данном примере сервера эта обработка сделана топорно, однако правильно было бы сперва считать все post-данные в MemoryStream, потом уже передавать управление POST-обработчику. Тем не менее по ряду причин и это решение не является идеальным. Во-первых, пост-данные могут быть большими. Это может быть файл, которые аплоадит клиент, и буферизация его в памяти может быть неэффективна или даже не возможна. В идеале необходимо создать некий тип поток-имитатора, который бы имел ограничение на длину content-length, а в остальном работал бы как обычный поток. Это позволит POST-обработчику извлекать данные из потока без накладных расходов при буфферизации в памяти. Однако это уже потребует намного больше кода. Пост-запросы во встроенных HTTP-серверах используются не так часто, поэтому мы просто ограничили входные данные 10 мегабайтами.
Еще одно упрощение нашего сервера заключается в типе возвращаемых клиенту данных (content-type). В протоколе HTTP, сервер всегда посылает браузеру MIME-тип (MIME-Type) данных, которые тот ожидает. В методе writeSuccess() видно, что наш сервер всегда посылает один и тот же тип text/html. Если необходимо возвращать другие типы содержимого, то потребуется доработка этого метода чтобы он посылал клиенту соответствующий тип перед началом отправки содержимого.
Заключение
Этот пример веб-сервера включает в себя обработку только самых основных возможностей протокола HTTP/1.0. Более продвинутые возможности HTTP включают в себя сжатие данных, сохранение сессий, частичная загрузка и многое другое. Тем не менее даже такой простой веб-сервера позволяет генерировать странички, которые понимаются современными браузерами.