Реализация веб-сервера будет разбита на две части: первая (класс HttpServer) будет отвечать за прием сообщений от клиентов, вторая (класс ClientSession) за их обработку.
Код класса HttpServer представлен ниже:
Приведенный код не должен вызвать затруднений. В нем создается серверный сокет, и при каждом новом подключении клиента его обработка делегируется очередному объекту ClientSession.
Полный код ClientSession приводится ниже, после идут небольшие пояснения к нему:
Первое, что делает ClientSession - это получает содержимое запроса и выводит его в стандартный поток вывода. Т.к. данная реализация не предусматривает реакции на параметры запроса, то тело запроса не представляет интереса и чтение самого запроса ограничивается только его заголовком:
Далее, из заголовка сообщения получается url запрашиваемого ресурса. В соответствии с протоколом http, url вытаскивается из первой строки заголовка как подстрока между первыми двумя пробелами. От url отрезаются параметры запроса (если они присутствуют).
Для удобства, корневой директорией сервера считается папка www, но это всего лишь несущественная условность.
Для ответа клиенту формируется простейший http заголовок, указывающий код ответа. Доступны два кода: 200 - если запрашиваемый ресурс был найден и всеми любимый 404 - если ресурса не оказалось. Дополнительно в заголовке указывается время и тот факт, что сервер не поддерживает докачку файлов (оба поля не существенны и приводятся чисто формально).
При отправке ответа ресурсы получаются несколько неординарным способом:
Это сделано для того, чтобы можно было уместить весь сервер в одном jar архиве.
Собранный проект вы можете взять здесь . Для запуска требуется java версии 6 и выше. Команда для запуска проекта: java -jar webserver.jar <номер порта>. Номер порта можно не указывать, тогда сервер будет запущен на 9999 порту:
Чтобы проверить работоспособность сервера, перейдите по адресу: http://localhost:9999/index.html В случае успеха должна открыться страница, которая лежит в папке www внутри jar архива с сервером. Для размещения собственных ресурсов на сервере просто добавьте их в папку www внутри архива и перейдите по ссылке http://localhost:9999/<путь>, где <путь> - это это путь к интересующему вас ресурсу относительно папки www внутри архива. Внимание! В <пути> в качестве разделителя необходимо использовать прямой слэш "/". Если путь будет содержать ссылку не на файл, а на директорию, будет выведено содержимое этой директории.
Содержимое, отправленное серверу можно увидеть в окне терминала из которого был запущен сервер. Помимо заголовка запроса будет выведен запрашиваемый адрес и код ответа сервера:
Для прекращения работы сервера воспользуйтесь стандартной комбинацией клавиш Ctrl+C - See more at: http://www.dokwork.ru/2012/06/http-java-25-web-server.html#sthash.rzBQbmhk.dpuf
Код класса HttpServer представлен ниже:
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
* Обрабатывает запросы от клиентов, возвращая файлы, указанные
* в url-path или ответ с кодом 404, если такой файл не найден.
*
*/
public class HttpServer {
/**
* Первым аргументом может идти номер порта.
*/
public static void main(String[] args) {
/* Если аргументы отсутствуют, порт принимает значение поумолчанию */
int port = DEFAULT_PORT;
if (args.length > 0) {
port = Integer.parseInt(args[0]);
}
/* Создаем серверный сокет на полученном порту */
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(port);
System.out.println("Server started on port: "
+ serverSocket.getLocalPort() + "\n");
} catch (IOException e) {
System.out.println("Port " + port + " is blocked.");
System.exit(-1);
}
/*
* Если порт был свободен и сокет был успешно создан, можно переходить к
* следующему шагу - ожиданию клинтов
*/
while (true) {
try {
Socket clientSocket = serverSocket.accept();
/* Для обработки запроса от каждого клиента создается
* отдельный объект и отдельный поток */
ClientSession session = new ClientSession(clientSocket);
new Thread(session).start();
} catch (IOException e) {
System.out.println("Failed to establish connection.");
System.out.println(e.getMessage());
System.exit(-1);
}
}
}
private static final int DEFAULT_PORT = 9999;
}
Полный код ClientSession приводится ниже, после идут небольшие пояснения к нему:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Date;
/**
* Обрабатывает запрос клиента.
*/
public class ClientSession implements Runnable {
@Override
public void run() {
try {
/* Получаем заголовок сообщения от клиента */
String header = readHeader();
System.out.println(header + "\n");
/* Получаем из заголовка указатель на интересующий ресурс */
String url = getURIFromHeader(header);
System.out.println("Resource: " + url + "\n");
/* Отправляем содержимое ресурса клиенту */
int code = send(url);
System.out.println("Result code: " + code + "\n");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public ClientSession(Socket socket) throws IOException {
this.socket = socket;
initialize();
}
private void initialize() throws IOException {
/* Получаем поток ввода, в который помещаются сообщения от клиента */
in = socket.getInputStream();
/* Получаем поток вывода, для отправки сообщений клиенту */
out = socket.getOutputStream();
}
/**
* Считывает заголовок сообщения от клиента.
*
* @return строка с заголовком сообщения от клиента.
* @throws IOException
*/
private String readHeader() throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuilder builder = new StringBuilder();
String ln = null;
while (true) {
ln = reader.readLine();
if (ln == null || ln.isEmpty()) {
break;
}
builder.append(ln + System.getProperty("line.separator"));
}
return builder.toString();
}
/**
* Вытаскивает идентификатор запрашиваемого ресурса из заголовка сообщения от
* клиента.
*
* @param header
* заголовок сообщения от клиента.
* @return идентификатор ресурса.
*/
private String getURIFromHeader(String header) {
int from = header.indexOf(" ") + 1;
int to = header.indexOf(" ", from);
String uri = header.substring(from, to);
int paramIndex = uri.indexOf("?");
if (paramIndex != -1) {
uri = uri.substring(0, paramIndex);
}
return DEFAULT_FILES_DIR + uri;
}
/**
* Отправляет ответ клиенту. В качестве ответа отправляется http заголовок и
* содержимое указанного ресурса. Если ресурс не указан, отправляется
* перечень доступных ресурсов.
*
* @param url
* идентификатор запрашиваемого ресурса.
* @return код ответа. 200 - если ресурс был найден, 404 - если нет.
* @throws IOException
*/
private int send(String url) throws IOException {
InputStream strm = HttpServer.class.getResourceAsStream(url);
int code = (strm != null) ? 200 : 404;
String header = getHeader(code);
PrintStream answer = new PrintStream(out, true, "UTF-8");
answer.print(header);
if (code == 200) {
int count = 0;
byte[] buffer = new byte[1024];
while((count = strm.read(buffer)) != -1) {
out.write(buffer, 0, count);
}
strm.close();
}
return code;
}
/**
* Возвращает http заголовок ответа.
*
* @param code
* код результата отправки.
* @return http заголовок ответа.
*/
private String getHeader(int code) {
StringBuffer buffer = new StringBuffer();
buffer.append("HTTP/1.1 " + code + " " + getAnswer(code) + "\n");
buffer.append("Date: " + new Date().toGMTString() + "\n");
buffer.append("Accept-Ranges: none\n");
buffer.append("\n");
return buffer.toString();
}
/**
* Возвращает комментарий к коду результата отправки.
*
* @param code
* код результата отправки.
* @return комментарий к коду результата отправки.
*/
private String getAnswer(int code) {
switch (code) {
case 200:
return "OK";
case 404:
return "Not Found";
default:
return "Internal Server Error";
}
}
private Socket socket;
private InputStream in = null;
private OutputStream out = null;
private static final String DEFAULT_FILES_DIR = "/www";
}
Первое, что делает ClientSession - это получает содержимое запроса и выводит его в стандартный поток вывода. Т.к. данная реализация не предусматривает реакции на параметры запроса, то тело запроса не представляет интереса и чтение самого запроса ограничивается только его заголовком:
if (ln == null || ln.isEmpty()) {
break;
}
private String getURIFromHeader(String header) {
int from = header.indexOf(" ") + 1;
int to = header.indexOf(" ", from);
String uri = header.substring(from, to);
int paramIndex = uri.indexOf("?");
if (paramIndex != -1) {
uri = uri.substring(0, paramIndex);
}
return DEFAULT_FILES_DIR + uri;
}
Для удобства, корневой директорией сервера считается папка www, но это всего лишь несущественная условность.
Для ответа клиенту формируется простейший http заголовок, указывающий код ответа. Доступны два кода: 200 - если запрашиваемый ресурс был найден и всеми любимый 404 - если ресурса не оказалось. Дополнительно в заголовке указывается время и тот факт, что сервер не поддерживает докачку файлов (оба поля не существенны и приводятся чисто формально).
private String getHeader(int code) {
StringBuffer buffer = new StringBuffer();
buffer.append("HTTP/1.1 " + code + " " + getAnswer(code) + "\n");
buffer.append("Date: " + new Date().toGMTString() + "\n");
buffer.append("Accept-Ranges: none\n");
buffer.append("\n");
return buffer.toString();
}
При отправке ответа ресурсы получаются несколько неординарным способом:
private int send(String url) throws IOException {
// Здесь можно было бы ожидать работу с классом File...
InputStream strm = HttpServer.class.getResourceAsStream(url);
int code = (strm != null) ? 200 : 404;
String header = getHeader(code);
PrintStream answer = new PrintStream(out, true, "UTF-8");
answer.print(header);
if (code == 200) {
int count = 0;
byte[] buffer = new byte[1024];
while((count = strm.read(buffer)) != -1) {
out.write(buffer, 0, count);
}
strm.close();
}
return code;
}
Собранный проект вы можете взять здесь . Для запуска требуется java версии 6 и выше. Команда для запуска проекта: java -jar webserver.jar <номер порта>. Номер порта можно не указывать, тогда сервер будет запущен на 9999 порту:
$>java -jar webserver.jar Server started on port: 9999
Чтобы проверить работоспособность сервера, перейдите по адресу: http://localhost:9999/index.html В случае успеха должна открыться страница, которая лежит в папке www внутри jar архива с сервером. Для размещения собственных ресурсов на сервере просто добавьте их в папку www внутри архива и перейдите по ссылке http://localhost:9999/<путь>, где <путь> - это это путь к интересующему вас ресурсу относительно папки www внутри архива. Внимание! В <пути> в качестве разделителя необходимо использовать прямой слэш "/". Если путь будет содержать ссылку не на файл, а на директорию, будет выведено содержимое этой директории.
Содержимое, отправленное серверу можно увидеть в окне терминала из которого был запущен сервер. Помимо заголовка запроса будет выведен запрашиваемый адрес и код ответа сервера:
$>java -jar webserver.jar Server started on port: 9999 GET /index.html HTTP/1.1 Host: localhost:9999 User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:13.0) Gecko/20100101 Firefox/13.0.1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: ru-ru,ru;q=0.8,en-us;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate Connection: keep-alive Resource: /www/index.html Result code: 200
Для прекращения работы сервера воспользуйтесь стандартной комбинацией клавиш Ctrl+C - See more at: http://www.dokwork.ru/2012/06/http-java-25-web-server.html#sthash.rzBQbmhk.dpuf
Комментариев нет:
Отправить комментарий