Пакет java для написание сервера. Простой HTTP-сервер в Java с использованием только Java SE API


Как вы наверное уже знаете - L2jServer сменил политику обновлений своих кодов и теперь обновления выходят примерно один раз в месяц, но тем не менее, код остается открытым и доступным для всех желающих изучить и установить Freya.


L2Teon проделал почти 150 корректировок кода с момента выхода прошлой ревизии java сервера Lineage 2 Interlude. Благодаря этому была повышена безопасность и отказоустойчивость сервера.
Обновлена защита заточки предметов, исправлен спавн-лист.
Исправлен баг с питомцами.
Отключена загрузка AI с ядра, теперь только с «\data\scripts\ai\».
Исправлены умения: Curse of Doom, Anchor, Mirage.
Исправлен баг со складом (не верно указаны данные FloodProtector).
Исправлен баг с исчезновением питомца после смерти.
Исправлены умения Spell Force и Batle Force.
Закрыта возможность чрезмерной заточки предметов.
Исправление записей таблицы NPC благодаря которому нет ошибок в консоли.


Разработка Java сервера Lineage 2 Gracia Epilogue или просто Plus продолжается, и сегодня для владельцев серверов на базе L2Open-Team доступно следующее обновление.
Добавлена опция отвечающая за шанс поднятия уровня Soul Crystal.
Исправлен третий этаж в Steel Citadel, точнее возможность на него перейти.
Добавлено восстановление маны Шаманом Орков при ударах.
Исправлено получение магической поддержки новичкам.
Исправление невидимости персонажей.
Были добавлены семена манора по Lineage 2 Gracia Epilogue.
Добавлен новый квест Pailaka Injured Dragon (необходим тест).
В движке реализовано умение "Семь Стрел (Seven Arrow)".
Добавлены Эвенты: Последний Герой и Захват Базы.
Администратору доступен телепорт ко всем Рейд Боссам Gracia.

В очередной раз вышло обновление сборки сервера Lineage 2 Epilogue от команды L2jServer с ревизией 4309 у ядра и 7529 у датапака.
Исправлено отображение окна действий при совершении трансформации.
Возможность активации кэширования всех имен существующих персонажей.
Исправление проблемы с отключением авто-сосок на одетом оружии.
Добавлена настройка дальности вещания событий с кораблями.
Доступна новая опция включения Чемпионов для спавна на карту.
Реализация клановых дирижаблей, исправление пути полета в квестах.
Добавлена поддержка более точного поиска двойных окон.
Был исправлена ошибка Геодвига из-за которой выводилось "Can"t see target".
В транспортных средствах теперь невозможно производить торговлю.
Дроплист проверяется при загрузке сервера на предмет "левых" предметов.

Последнее обновление: 20.09.2018

Класс Java Bean должен соответствовать ряду ограничений:

    иметь конструктор, который не принимает никаких параметров

    определять для всех свойств, которые используются в jsp, методы геттеры и сеттеры

    названия геттеров и сеттеров должны соответствовать условностям: перед именем переменной добавляется get (для геттера) и set (для сеттера), а название переменной включается с большой буквы. Например, если переменная называется firstName, то функции геттера и сеттера должны называться соответственно getFirstName и setFirstName.

    Однако для переменных типа boolean для функции геттера используется вместо get приставка is. Например, переменная enabled и геттер isEnabled.

    реализовать интерфейс Serializable или Externalizable

Рассмотрим, как использовать классы JavaBean. Допустим, у нас есть следующая структура:

В папке Java Resources/src расположен класс User со следующим кодом:

Import java.io.Serializable; public class User implements Serializable { private static final long serialVersionUID = 2041275512219239990L; private String name; private int age; public User() { this.name = ""; this.age = 0; } public User(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }

Данный класс представляет пользователя и является классом Java Bean: он реализует интерфейс Serializable, имеет конструктор без параметров, а его методы - геттеры и сеттеры, которые предоставляют доступ к переменным name и age, соответствуют условностям.

В папке WebContent определена страница user.jsp . Определим в ней следующий код:

User Java Bean Page

Name: ${user.name}

Age: ${user.age}

Данная страница jsp получает извне объект user и с помощью синтаксиса EL выводит значения его свойств. Стоит обратить внимание, что здесь идет обращение к переменным name и age, хотя они являются приватными.

В папке Java Resources/src в файле HelloServlet.java определен сервлет HelloServlet:

Import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet("/hello") public class HelloServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { User tom = new User("Tom", 25); request.setAttribute("user", tom); getServletContext() .getRequestDispatcher("/user.jsp") .forward(request, response); } }

Сервлет создает объект User. Для передачи его на страницу user.jsp устанавливается атрибут "user" через вызов request.setAttribute("user", tom) . Далее происходит перенаправление на страницу user.jsp. И, таким образом, страница получит данные из сервлета.

Есть ли способ создать очень простой HTTP-сервер (поддерживающий только GET/POST) в Java, используя только API Java SE, без написания кода для ручного анализа HTTP-запросов и отформатирования HTTP-ответов вручную? Java SE API прекрасно инкапсулирует функциональность HTTP-клиента в HttpURLConnection, но существует ли аналоговый для HTTP-сервер функционал?

Просто, чтобы быть ясным, проблема, с которой я сталкиваюсь с множеством примеров ServerSocket, которые я видел в Интернете, заключается в том, что они выполняют собственный алгоритм синтаксического анализа/отклика запросов и обработку ошибок, что является утомительным, подверженным ошибкам и вряд ли быть всеобъемлющим, и я стараюсь избегать этого по этим причинам.

В качестве примера ручной HTTP-манипуляции, которую я пытаюсь избежать:

18 ответов

Начиная с Java SE 6 в Sun Oracle JRE имеется встроенный HTTP-сервер. В сводке пакета com.sun.net.httpserver описаны участвующие классы и приведены примеры.

Вот начальный пример, скопированный из их документов (тем не менее, всем, кто пытается его редактировать, потому что это ужасный кусок кода, пожалуйста, не копируйте, а не мой, более того, вы никогда не должны редактировать цитаты, если они не изменились. в первоисточнике). Вы можете просто скопировать и запустить его на Java 6+.

package com.stackoverflow.q3732109; import java.io.IOException; import java.io.OutputStream; import java.net.InetSocketAddress; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; public class Test { public static void main(String args) throws Exception { HttpServer server = HttpServer.create(new InetSocketAddress(8000), 0); server.createContext("/test", new MyHandler()); server.setExecutor(null); // creates a default executor server.start(); } static class MyHandler implements HttpHandler { @Override public void handle(HttpExchange t) throws IOException { String response = "This is the response"; t.sendResponseHeaders(200, response.length()); OutputStream os = t.getResponseBody(); os.write(response.getBytes()); os.close(); } } }

Следует отметить, что часть response.length() в их примере плохая, это должна была быть response.getBytes().length . Даже в этом getBytes() метод getBytes() должен явно указывать кодировку, которую вы затем указываете в заголовке ответа. Увы, хотя и вводящий в заблуждение начинающих, в конце концов, это всего лишь базовый пример.

Выполните его и перейдите по адресу http://localhost: 8000/test, и вы увидите следующий ответ:

Что касается использования com.sun.* , Обратите внимание, что это, в отличие от того, что думают некоторые разработчики, абсолютно не запрещено общеизвестными часто задаваемыми вопросами. Почему разработчики не должны писать программы, называющие "солнечные" пакеты . Этот sun.misc.BASE64Encoder часто задаваемых вопросов касается пакета sun.* (Такого как sun.misc.BASE64Encoder) для внутреннего использования Oracle JRE (который, таким образом, уничтожит ваше приложение при запуске его на другом JRE), а не пакет com.sun.* . Sun/Oracle также просто разрабатывает программное обеспечение поверх API Java SE, как и любая другая компания, такая как Apache и так далее. Использование com.sun.* рекомендуется (но не запрещено), когда это касается реализации определенного Java API, такого как GlassFish (Java EE impl), Mojarra (JSF impl), Джерси (JAX-RS impl) и т.д.,

Решение com.sun.net.httpserver не переносится через JRE. Лучше использовать официальный API веб-сервисов в javax.xml.ws для загрузки минимального HTTP-сервера...

Import java.io._ import javax.xml.ws._ import javax.xml.ws.http._ import javax.xml.transform._ import javax.xml.transform.stream._ @WebServiceProvider @ServiceMode(value=Service.Mode.PAYLOAD) class P extends Provider { def invoke(source: Source) = new StreamSource(new StringReader("

Hello There!

")); } val address = "http://127.0.0.1:8080/" Endpoint.create(HTTPBinding.HTTP_BINDING, new P()).publish(address) println("Service running at "+address) println("Type +[C] to quit!") Thread.sleep(Long.MaxValue)

EDIT: это действительно работает! Вышеприведенный код выглядит как Groovy или что-то в этом роде. Вот перевод на Java, который я тестировал:

Import java.io.*; import javax.xml.ws.*; import javax.xml.ws.http.*; import javax.xml.transform.*; import javax.xml.transform.stream.*; @WebServiceProvider @ServiceMode(value = Service.Mode.PAYLOAD) public class Server implements Provider { public Source invoke(Source request) { return new StreamSource(new StringReader("

Hello There!

Посмотрите на веб-сервер "Jetty" Jetty . Превосходная часть программного обеспечения с открытым исходным кодом, которая, похоже, отвечает всем вашим требованиям.

Если вы настаиваете на сворачивании своих собственных, посмотрите на класс "httpMessage".

Мне нравится этот вопрос, потому что это область, где постоянно развиваются инновации, и всегда нужно иметь легкий сервер, особенно когда речь идет о встроенных серверах на небольших (er) устройствах. Я думаю, что ответы делятся на две широкие группы.

  • Thin-server : статический контент на сервере с минимальной обработкой, обработкой контекста или сеансом.
  • Малый сервер : якобы a имеет много http-подобных свойств сервера с небольшим размером, поскольку вы можете уйти.

Хотя я мог бы подумать о библиотеках HTTP, таких как: Jetty , Apache Http Components , Netty и другие, чтобы быть более похожими на необработанные средства обработки HTTP. Маркировка очень субъективна и зависит от того, что вы делали для небольших сайтов. Я делаю это различие в духе вопроса, особенно замечание о...

  • "... без написания кода для ручного анализа HTTP-запросов и вручную отклика HTTP-ответов..."

Эти исходные инструменты позволяют делать это (как описано в других ответах). Они действительно не поддаются готовому дизайну, создавая легкий, встроенный или мини-сервер. Мини-сервер - это то, что может дать вам аналогичную функциональность полнофункциональному веб-серверу (например, Tomcat) без колоколов и свистов, низкий объем, хорошая производительность в 99% случаев. Тонкий сервер кажется ближе к оригинальной фразе, чуть более сырой, возможно, с ограниченным набором функций, достаточным для того, чтобы вы выглядели неплохо в 90% случаев. Мое представление о сыром будет заставлять меня выглядеть хорошо 75% - 89% времени без дополнительного дизайна и кодирования. Я думаю, что если/когда вы достигнете уровня WAR файлов, мы оставили "маленький" для серверов bonsi, который выглядит как все, что делает большой сервер.

Параметры тонкого сервера

  • UniRest (несколько языков)
  • NanoHTTPD (всего один файл)

Параметры мини-сервера:

  • Spark Java ... Хорошие вещи возможны благодаря множеству вспомогательных конструкций, таких как фильтры, шаблоны и т.д.
  • MadVoc ... стремится быть бонсай и вполне может быть таким; -)

Среди других вещей, которые следует учитывать, я бы включил аутентификацию, валидацию, интернационализацию, используя что-то вроде FreeMaker или другой инструмент шаблона для рендеринга страницы вывод. В противном случае управление редактированием и параметризацией HTML, вероятно, приведет к тому, что работа с HTTP будет выглядеть как noughts-n-crosses. Естественно, все зависит от того, насколько вы гибки. Если это факсимильная машина с меню, она может быть очень простой. Чем больше взаимодействий, тем "более толстой" должна быть ваша структура. Хороший вопрос, удачи!

Когда-то я искал нечто похожее - легкий, но полностью функциональный HTTP-сервер, который я мог бы легко встроить и настроить. Я нашел два типа потенциальных решений:

  • Полные серверы, которые не такие легкие или простые (для экстремального определения легкости).
  • Поистине легкие серверы, которые не совсем HTTP-серверы, но прославленные примеры ServerSocket, которые даже не поддерживают RFC-совместимость и не поддерживают обычно необходимые базовые функции.

Вы можете встраивать его в любой проект в виде одиночного (если довольно длинного) исходного файла или в виде баннера размером ~ 50 КБ (~ 35 КБ) без каких-либо зависимостей. Он стремится быть совместимым с RFC и включает в себя обширную документацию и множество полезных функций при сохранении раздувания до минимума.

Возможности включают в себя: виртуальные хосты, файлы с диска, сопоставления типа mime через стандартный файл mime.types, генерацию индекса каталога, приветственные файлы, поддержку всех HTTP-методов, условные ETags и поддержку заголовков If- *, кодирование с кодировкой передачи, gzip/сбрасывать сжатие, базовые HTTPS (как предоставлено JVM), частичный контент (продолжение загрузки), обработку мультифайлов/форм файлов для загрузки файлов, несколько обработчиков контекстов через API или аннотации, разбор параметров (строка запроса или x-www-form- urlencoded body) и т.д.

Я надеюсь, что другие считают это полезным:-)

Возможно создание httpserver, обеспечивающего базовую поддержку сервлетов J2EE только с JDK и сервлета api всего за несколько строк кода.

Я нашел это очень полезным для сервлетов для тестирования модулей, поскольку он запускается намного быстрее, чем другие легкие контейнеры (мы используем причал для производства).

Большинство очень легких httpservers не обеспечивают поддержку сервлетов, но мы нуждаемся в них, поэтому я думал, что буду делиться.

В приведенном ниже примере представлена ​​базовая поддержка сервлетов или выбрасывается и UnsupportedOperationException для тех вещей, которые еще не реализованы. Он использует com.sun.net.httpserver.HttpServer для базовой поддержки http.

Import java.io.*; import java.lang.reflect.*; import java.net.InetSocketAddress; import java.util.*; import javax.servlet.*; import javax.servlet.http.*; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; @SuppressWarnings("deprecation") public class VerySimpleServletHttpServer { HttpServer server; private String contextPath; private HttpHandler httpHandler; public VerySimpleServletHttpServer(String contextPath, HttpServlet servlet) { this.contextPath = contextPath; httpHandler = new HttpHandlerWithServletSupport(servlet); } public void start(int port) throws IOException { InetSocketAddress inetSocketAddress = new InetSocketAddress(port); server = HttpServer.create(inetSocketAddress, 0); server.createContext(contextPath, httpHandler); server.setExecutor(null); server.start(); } public void stop(int secondsDelay) { server.stop(secondsDelay); } public int getServerPort() { return server.getAddress().getPort(); } } final class HttpHandlerWithServletSupport implements HttpHandler { private HttpServlet servlet; private final class RequestWrapper extends HttpServletRequestWrapper { private final HttpExchange ex; private final Map postData; private final ServletInputStream is; private final Map attributes = new HashMap<>(); private RequestWrapper(HttpServletRequest request, HttpExchange ex, Map postData, ServletInputStream is) { super(request); this.ex = ex; this.postData = postData; this.is = is; } @Override public String getHeader(String name) { return ex.getRequestHeaders().getFirst(name); } @Override public Enumeration getHeaders(String name) { return new Vector(ex.getRequestHeaders().get(name)).elements(); } @Override public Enumeration getHeaderNames() { return new Vector(ex.getRequestHeaders().keySet()).elements(); } @Override public Object getAttribute(String name) { return attributes.get(name); } @Override public void setAttribute(String name, Object o) { this.attributes.put(name, o); } @Override public Enumeration getAttributeNames() { return new Vector(attributes.keySet()).elements(); } @Override public String getMethod() { return ex.getRequestMethod(); } @Override public ServletInputStream getInputStream() throws IOException { return is; } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } @Override public String getPathInfo() { return ex.getRequestURI().getPath(); } @Override public String getParameter(String name) { String arr = postData.get(name); return arr != null ? (arr.length > 1 ? Arrays.toString(arr) : arr) : null; } @Override public Map getParameterMap() { return postData; } @Override public Enumeration getParameterNames() { return new Vector(postData.keySet()).elements(); } } private final class ResponseWrapper extends HttpServletResponseWrapper { final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); final ServletOutputStream servletOutputStream = new ServletOutputStream() { @Override public void write(int b) throws IOException { outputStream.write(b); } }; private final HttpExchange ex; private final PrintWriter printWriter; private int status = HttpServletResponse.SC_OK; private ResponseWrapper(HttpServletResponse response, HttpExchange ex) { super(response); this.ex = ex; printWriter = new PrintWriter(servletOutputStream); } @Override public void setContentType(String type) { ex.getResponseHeaders().add("Content-Type", type); } @Override public void setHeader(String name, String value) { ex.getResponseHeaders().add(name, value); } @Override public javax.servlet.ServletOutputStream getOutputStream() throws IOException { return servletOutputStream; } @Override public void setContentLength(int len) { ex.getResponseHeaders().add("Content-Length", len + ""); } @Override public void setStatus(int status) { this.status = status; } @Override public void sendError(int sc, String msg) throws IOException { this.status = sc; if (msg != null) { printWriter.write(msg); } } @Override public void sendError(int sc) throws IOException { sendError(sc, null); } @Override public PrintWriter getWriter() throws IOException { return printWriter; } public void complete() throws IOException { try { printWriter.flush(); ex.sendResponseHeaders(status, outputStream.size()); if (outputStream.size() > 0) { ex.getResponseBody().write(outputStream.toByteArray()); } ex.getResponseBody().flush(); } catch (Exception e) { e.printStackTrace(); } finally { ex.close(); } } } public HttpHandlerWithServletSupport(HttpServlet servlet) { this.servlet = servlet; } @SuppressWarnings("deprecation") @Override public void handle(final HttpExchange ex) throws IOException { byte inBytes = getBytes(ex.getRequestBody()); ex.getRequestBody().close(); final ByteArrayInputStream newInput = new ByteArrayInputStream(inBytes); final ServletInputStream is = new ServletInputStream() { @Override public int read() throws IOException { return newInput.read(); } }; Map parsePostData = new HashMap<>(); try { parsePostData.putAll(HttpUtils.parseQueryString(ex.getRequestURI().getQuery())); // check if any postdata to parse parsePostData.putAll(HttpUtils.parsePostData(inBytes.length, is)); } catch (IllegalArgumentException e) { // no postData - just reset inputstream newInput.reset(); } final Map postData = parsePostData; RequestWrapper req = new RequestWrapper(createUnimplementAdapter(HttpServletRequest.class), ex, postData, is); ResponseWrapper resp = new ResponseWrapper(createUnimplementAdapter(HttpServletResponse.class), ex); try { servlet.service(req, resp); resp.complete(); } catch (ServletException e) { throw new IOException(e); } } private static byte getBytes(InputStream in) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); byte buffer = new byte; while (true) { int r = in.read(buffer); if (r == -1) break; out.write(buffer, 0, r); } return out.toByteArray(); } @SuppressWarnings("unchecked") private static T createUnimplementAdapter(Class httpServletApi) { class UnimplementedHandler implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object args) throws Throwable { throw new UnsupportedOperationException("Not implemented: " + method + ", args=" + Arrays.toString(args)); } } return (T) Proxy.newProxyInstance(UnimplementedHandler.class.getClassLoader(), new Class { httpServletApi }, new UnimplementedHandler()); } }

Я настоятельно рекомендую изучить , особенно если вам не нужны возможности сервлета, а просто доступ к объектам запроса/ответа, Если вам нужен REST, вы можете положить Джерси поверх него, если вам нужно вывести HTML или аналогичный там Freemarker. Мне очень нравится то, что вы можете сделать с этой комбинацией, и относительно немного API для изучения.

Этот код лучше нашего, вам нужно только добавить 2 библиотеки: javax.servelet.jar и org.mortbay.jetty.jar .

Причал класса:

Package jetty; import java.util.logging.Level; import java.util.logging.Logger; import org.mortbay.http.SocketListener; import org.mortbay.jetty.Server; import org.mortbay.jetty.servlet.ServletHttpContext; public class Jetty { public static void main(String args) { try { Server server = new Server(); SocketListener listener = new SocketListener(); System.out.println("Max Thread:" + listener.getMaxThreads() + " Min Thread:" + listener.getMinThreads()); listener.setHost("localhost"); listener.setPort(8070); listener.setMinThreads(5); listener.setMaxThreads(250); server.addListener(listener); ServletHttpContext context = (ServletHttpContext) server.getContext("/"); context.addServlet("/MO", "jetty.HelloWorldServlet"); server.start(); server.join(); /*//We will create our server running at http://localhost:8070 Server server = new Server(); server.addListener(":8070"); //We will deploy our servlet to the server at the path "/" //it will be available at http://localhost:8070 ServletHttpContext context = (ServletHttpContext) server.getContext("/"); context.addServlet("/MO", "jetty.HelloWorldServlet"); server.start(); */ } catch (Exception ex) { Logger.getLogger(Jetty.class.getName()).log(Level.SEVERE, null, ex); } } }




Лучший способ понять устройство и принцип работы чего-либо – сделать это что-то самому.

Заинтересовавшись однажды сетевыми технологиями и, среди прочего, серверами, я пришёл к мысли, что было бы неплохо написать одн такой сервер самому, используя 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; 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-сервера является одной из простейших, она может быть легко доработана путём добавления графического интерфейса пользователя, количества поддерживаемых расширений, ограничителя подключений и т.п. Одним словом – простор для творчества остаётся огромным. Дерзайте.

Вы можете помочь и перевести немного средств на развитие сайта

Когда я начинал работать в Java EE, серверная инфраструктура казалась мне загадочным капризным монстром. И это при том, что в программирование я пришёл из админов. Залил ear в Resin, проверь, поднялся ли? Успешно ли распаковался? Однажды мы с админами потратили полночи чтобы понять, почему после успешного вроде бы деплоя на запросы отвечает по-прежнему старая копия приложения. При таких танцах с бубном переход на GlassFish казался хорошим решением, а отказ от ejb - ещё лучшим. Кто-то из моих коллег радовался могуществу Spring, кто-то искал что-то попроще... Я писал сервлеты под tomcat, а когда делал что-то совсем маленькое - просто встраивал jetty.
Чем проще, тем лучше. Понятно, что есть нетривиальные требования, есть сложные задачи. Но признаемся себе, сколько простых ненагруженных сервисов вы реализовали используя слишком мощные и сложные инструменты? "Я привык к этому фреймфорку и не хочу терять скилл" - слышу я обычно. "Ты уйдёшь и тот, кто будет поддерживать этот монстрокод, проклянёт тебя" - думаю я в ответ.
Может я и не убедил вас писать простые вещи простыми средствами, но посмотрите все-таки как можно сделать серверное java-приложение вообще без сервера. Это, в конце концов, интересно.

Как, вообще без сервера? Писать обработку запросов самому?

Ну, нет, что вы. Я за простые решения, но не за велосипеды. Все уже написано и, более того, уже установлено. Java-сервер уже есть в вашем jdk. Просто используйте его)

Import com.sun.net.httpserver.HttpServer ; import java.io.IOException ; import java.net.InetSocketAddress ; import java.util.concurrent.Executors ; public class Main { private static final int PORT = 9090 ; public static void main(String args) throws IOException { int port = PORT; if (args.length > 0 ) { try { port = Integer .parseInt (args[ 0 ] ) ; } catch (Exception e) { e.printStackTrace () ; } } HttpServer server = HttpServer.create (new InetSocketAddress(port) , 0 ) ; server.createContext ("/publish" , new PublishHandler() ) ; server.createContext ("/subscribe" , new SubscribeHandler() ) ; server.setExecutor (Executors.newCachedThreadPool () ) ; server.start () ; System .out .println ("Server is listening on port " + port) ; } }
com.sun.net.HttpServer - вот все что нам нужно. Наше приложение будет слушать запросы на порту 9090 или том, что будет указан java -jar наш.jar тут. Наши обработчики мы навешиваем на контексты перед стартом сервера. Все предельно просто. Чтобы в обработчиках сосредоточиться на бизнес-логике, сделаем им абстрактный предок, где порешаем все мелочи вроде вычитывания параметров зарпроса и логирования пары запрос-ответ:

Import com.sun.net.httpserver.HttpExchange ; import com.sun.net.httpserver.HttpHandler ; import java.io.IOException ; import java.io.OutputStream ; import java.text.SimpleDateFormat ; import java.util.Date ; import java.util.HashMap ; import java.util.Map ; import java.util.UUID ; public abstract class HttpHandlerBased implements HttpHandler { protected abstract String doGet(HttpExchange exchange, Map< String , String> params) ; @Override public void handle(HttpExchange exchange) throws IOException { String requestMethod = exchange.getRequestMethod () ; if (requestMethod.equalsIgnoreCase ("GET" ) ) { String uriStr = exchange.getRequestURI () .getQuery () ; String id = UUID.randomUUID () .toString () .replace ("-" , "" ) ; log(" " + uriStr) ; Map< String , String> pars = new HashMap< String , String> () ; String pairs; if (uriStr != null && uriStr.contains ("=" ) ) { if (uriStr.contains ("&" ) ) pairs = uriStr.split ("&" ) ; else pairs = new String { uriStr} ; for (String pair : pairs) { String p = pair.split ("=" ) ; if (p.length == 2 ) { pars.put (p[ 0 ] , p[ 1 ] ) ; } } } String resp = doGet(exchange, pars) ; log(" " + resp) ; exchange.sendResponseHeaders (200 , resp.length () ) ; OutputStream body = exchange.getResponseBody () ; body.write (resp.getBytes ("UTF-8" ) ) ; body.close () ; } } public static void log(String msg) { System .out .println (new SimpleDateFormat ("HH:mm:ss:SSS dd.MM.yy" ) .format (new Date () ) + " - " + msg) ; } }
Имея "за плечами" такого предка, нам останется реализовать метод doGet, в каждом конкретном "сервлете". Метод POST я тут не обрабатываю потому что он мне не нужен. Но обработать его не сложнее, можете попробовать сами.

Итак, что же мы получаем?

Для "секретного web-сервера", по сути внутреннего API java, мы имеем очень приятные инструменты для работы с данными запроса, заголовками ответа и т.п. Все это не требует никакой установки, настройки, есть прямо "в коробке" и готово к работе. Чем не инструмент для простого web-сервиса? Конечно, когда мы проверим свою идею и задумаемся о продакшене, можно перенести свою бизнес-логику в "честные" сервлеты и использовать тот же tomcat или resin. Почему бы и нет? Но если проект не пойдёт в продакшн мы скажем себе спасибо за то, что не привлекали админов, не тратили дни на развертывание и настройку окружения, а просто взяли и сделали то что было нужно. И не более того.







2024 © gtavrl.ru.