простой HTTP-сервер на Java с использованием только API Java SE - PullRequest
303 голосов
/ 17 сентября 2010

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

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

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

http://java.sun.com/developer/technicalArticles/Networking/Webserver/WebServercode.html

Ответы [ 18 ]

439 голосов
/ 17 сентября 2010

Начиная с Java SE 6, в Sun Oracle JRE встроен HTTP-сервер. 1004 * Сводка пакета обрисовывает в общих чертах вовлеченные классы и содержит примеры.

Вот пример запуска copypasted из их документов (для всех людей, пытающихся отредактировать его,потому что это уродливый кусок кода, пожалуйста, не надо, это копия, а не моя, более того, вы никогда не должны редактировать цитаты, если они не изменились в исходном коде).Вы можете просто скопировать и запустить его на 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() должен явно указывать кодировку, которую вы затем указываете в заголовке ответа.Увы, хотя это и неверный стартер, это всего лишь базовый пример.

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

Это ответ


Что касается использования com.sun.* классов, обратите внимание, что это, в отличие от того, что думают некоторые разработчики, абсолютно не запрещено общеизвестным FAQ Почему разработчики не должны писать программы, которые называют «солнечными» пакетами .Этот FAQ относится к пакету 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)и т. д.

41 голосов
/ 17 сентября 2010

Выезд NanoHttpd

"NanoHTTPD - это легкий HTTP-сервер, предназначенный для встраивания в другие приложения, выпущенный по модифицированной лицензии BSD.

Он разрабатывается на Github и использует Apache Maven для сборки и модульного тестирования "

24 голосов
/ 08 июня 2014

Решение 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[Source] {
  def invoke(source: Source) = new StreamSource( new StringReader("<p>Hello There!</p>"));
}

val address = "http://127.0.0.1:8080/"
Endpoint.create(HTTPBinding.HTTP_BINDING, new P()).publish(address)

println("Service running at "+address)
println("Type [CTRL]+[C] to quit!")

Thread.sleep(Long.MaxValue)

РЕДАКТИРОВАТЬ: это действительно работает!Приведенный выше код выглядит как 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<Source> {

    public Source invoke(Source request) {
        return  new StreamSource(new StringReader("<p>Hello There!</p>"));
    }

    public static void main(String[] args) throws InterruptedException {

        String address = "http://127.0.0.1:8080/";
        Endpoint.create(HTTPBinding.HTTP_BINDING, new Server()).publish(address);

        System.out.println("Service running at " + address);
        System.out.println("Type [CTRL]+[C] to quit!");

        Thread.sleep(Long.MAX_VALUE);
    }
}
21 голосов
/ 17 сентября 2010

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

Если вы настаиваете на том, чтобы кататься самостоятельно, взгляните на класс "httpMessage".

18 голосов
/ 04 сентября 2014

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

  1. Тонкий сервер : статический контент на сервере с минимальной обработкой, обработкой контекста или сеанса.
  2. Small-server : якобы a обладает многими httpD-подобными серверными качествами с минимальной занимаемой площадью.

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

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

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

Опции тонкого сервера

Опции мини-сервера:

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

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

16 голосов
/ 26 февраля 2016

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

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

Итак ... Я решил написать JLHTTP - JavaОблегченный HTTP-сервер .

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

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

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

9 голосов
/ 02 апреля 2015

Spark является самым простым, вот краткое руководство: http://sparkjava.com/

8 голосов
/ 19 января 2014
7 голосов
/ 28 ноября 2013

Можно создать http-сервер, который обеспечивает базовую поддержку для сервлетов J2EE, используя всего лишь JDK и API сервлетов в нескольких строках кода.

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

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

В приведенном ниже примере представлена ​​базовая поддержка сервлетов, или throws и 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<String, String[]> postData;
        private final ServletInputStream is;
        private final Map<String, Object> attributes = new HashMap<>();

        private RequestWrapper(HttpServletRequest request, HttpExchange ex, Map<String, String[]> 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<String> getHeaders(String name) {
            return new Vector<String>(ex.getRequestHeaders().get(name)).elements();
        }

        @Override
        public Enumeration<String> getHeaderNames() {
            return new Vector<String>(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<String> getAttributeNames() {
            return new Vector<String>(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[0]) : null;
        }

        @Override
        public Map<String, String[]> getParameterMap() {
            return postData;
        }

        @Override
        public Enumeration<String> getParameterNames() {
            return new Vector<String>(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<String, String[]> 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<String, String[]> 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[1024];
        while (true) {
            int r = in.read(buffer);
            if (r == -1)
                break;
            out.write(buffer, 0, r);
        }
        return out.toByteArray();
    }

    @SuppressWarnings("unchecked")
    private static <T> T createUnimplementAdapter(Class<T> 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());
    }
}
6 голосов
/ 17 сентября 2010

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

...