Лучший способ понять устройство и принцип работы чего-либо – сделать это что-то самому.
Заинтересовавшись однажды сетевыми технологиями и, среди прочего, серверами, я пришёл к мысли, что было бы неплохо написать одн такой сервер самому, используя Java.
Однако большая часть Java-литературы, что попадалась мне на глаза, если и объясняла нечто по теме, то лишь в самых общих чертах, не идя далее обмена текстовыми сообщениями между клиент-серверными приложениями. В интернете подобная информация встречалась не намного чаще, в основном в виде крупиц знаний на обучающийх сайтах или отрывочных сведений от пользователей разнообразных форумов.
Таким образом, собрав эти знания воедино и написав таки удобоваримое серверное прилоение, спобоное обрабатывать браузерные запросы, я решил сделать выжимку из подобных знаний и поэтапно описать процесс создания простейшего web-сервера на Java.
Надеюсь, в этой статье найдутся полезные знания для начинающих Java-программистов и других людей, изучающих связанные технологии.
Итак, поскольку программа предполагает простейшие функции сервера, она будет состоять из одного класса без графического интерфейса. Этот класс (Server) наследует поток и имеет одно поле – сокет:
class Server extends Thread { Socket s; }
В главном методе создаём новый ServerSocket и задаём для него порт (в данном случае использован порт 1025) и в бесконечном цикле ожидаем соединения с клиентом. При наличии соединения мы создаем новый поток, передавая ему соответствующий сокет. В случае неудачи выводим сообщение об ошибке:
try { ServerSocket server = new ServerSocket(1025); while(true) { new Server(server.accept()); } } catch(Exception e) { System.out.println("Error: " + e); }
Для того, чтобы сделать возможным создание нового потока подобным образом мы, естественно, должны описать для него соответствующий конструктор. В конструкторе мы маркируем поток как демон и здесь же запускаем:
public Server(Socket socket) { this.socket = socket; setDaemon(true); start(); }
Далее описываем функционал потока в методе run(): в первую очередь, создаем из сокета поток исходящих и входящих данных:
public void run() { try { InputStream input = socket.getInputStream(); OutputStream output = socket.getOutputStream();
Для считывания входящих данных мы будем применять буфер, представляющий из себя байт-массив определёной размерности. Создание подобного буфера не является обязательным, т.к. возможно принимать входящие данные и другими способами, не лимитируя количество принимаемой информации – но для простейшего web-сервера, описанного здесь, вполне достаточно 64 кбайт для получения необходимых данных от клиента.
Помимо байт-массива, мы также создаем int переменную, которая будет хранить в себе количество реально принятых буфером байт. Это необходимо для того, чтобы в последствии особыми образом создать строку клиентского запроса из полученных данных:
byte [] buffer = new byte[64*1024]; int bytes = input.read(buffer); String request = new String(buf, 0, r);
В строке request будет содержаться http-запрос от клиента. Среди прочей информации, содержащейся в данном запросе, нас в данный момент интересует адрес запрашиваемого файла и его расширение.
Не вдаваясь в подробности структуры http-запроса скажу лишь, что нужная нам информация будет находиться в первой строчке данного запроса приблизительно в таком виде:
GET /index.html HTTP/1.1
В данном примере запрашивается страница на сервере по адресу
/index.html
Страница с таким адресом выдается на большинстве серверов по умолчанию. Наша задача – с помощью собственноручно написанного метода getPath() вычленить этот адрес из запроса. Существует множество вариантов подобного метода и здесь их приводить нет смысла. Ключевой момент здесь состоит в том, что получив путь до нужного файла и записав его в строковую переменную path, мы можем попробовать создать на основе этих данных файл и, в случае успеха, вернуть этот файл, а в случае неудачи – вернуть специфическое сообщение об ошибке:
String path = getPath(request); File file = new File(path);
Проверяем, является ли данный файл дирекорией. Если такой файл существует и является директорией, то мы возвращаем упомянутый выше файл по умолчанию – index.html:
boolean exists = !file.exists(); if(!exists) if(file.isDirectory()) if(path.lastIndexOf(""+File.separator) == path.length()-1) { path = path + "index.html"; } else { path = path + File.separator + "index.html"; file = new File(path); exists = !file.exists(); }
Если файла по указанному адресу не существует, то мы создаем http-ответ в строке response с указанием того, что файл не найден, добавляя в нужном порядке следующие заголовки:
if(exists){ String response = "HTTP/1.1 404 Not Found\n"; response +="Date: " + new Date() + "\n"; response +="Content-Type: text/plain\n"; response +="Connection: close\n"; response +="Server: Server\n"; response +="Pragma: no-cache\n\n"; response += "File " + path + " Not Found!";
После формирования строки response мы отправляем их клиенту и закрываем соединение:
output.write(response.getBytes()); socket.close(); return; }
Если же файл существует, то перед формированием ответа необходимо выяснить его расширение и, следовательно, MIME-тип. Для начала мы выясним индекс точки, стоящей перед расширением файла и сохраним его в int-переменную.
int ex = path.lastIndexOf(".");
Затем вычленим расширение файла, стоящее после неё. Список возможным MIME-типов можно расширить, но в данном случае буде использовать всего по одной из форм 3-х форматов: html, jpeg и gif. По умолчанию будем использовать MIME-тип для текста:
String mimeType = “text/plain”; if(ex > 0) { String format = path.substring(r); if(format.equalsIgnoreCase(".html")) mimeType = "text/html"; else if(format.equalsIgnoreCase(".jpeg")) mimeType = "image/jpeg"; else if(format.equalsIgnoreCase(".gif")) mimeType = "image/gif";
Формируем ответ клиенту:
String response = "HTTP/1.1 200 OK\n"; response += "Last-Modified: " + new Date(file.lastModified())) + "\n"; response += "Content-Length: " + file.length() + "\n"; response += "Content-Type: " + mimeType + "\n"; response +="Connection: close\n";
В конце заголовков обязательно должно быть две пустые строки, иначе ответ не будет корректны образом обработан клиентом.
response += "Server: Server\n\n"; output.write(response.getBytes());
Для отправки самого файла можно использовать следующую конструкцию:
FileInputStream fis = new FileInputStream(path); int write = 1; while(write > 0) { write = fis.read(buffer); if(write > 0) output.write(buffer, 0, write); } fis.close(); socket.close(); }
Наконец, завершаем блок try-catch, указанный в начале.
catch(Exception e) { e.printStackTrace(); } }
Поскольку, как уже было сказано, данная реализация web-сервера является одной из простейших, она может быть легко доработана путём добавления графического интерфейса пользователя, количества поддерживаемых расширений, ограничителя подключений и т.п. Одним словом – простор для творчества остаётся огромным. Дерзайте.
ссылка на оригинал статьи http://habrahabr.ru/post/267031/
Добавить комментарий