Рубрики
Без рубрики

Руководство по NanoHttpd

Исследуйте легкий веб-сервер с открытым исходным кодом, написанный на Java – NanoHttpd.

Автор оригинала: baeldung.

1. введение

NanoHttpd -это легкий веб-сервер с открытым исходным кодом, написанный на Java.

В этом руководстве мы создадим несколько API-интерфейсов REST, чтобы изучить его функции.

2. Настройка проекта

Давайте добавим зависимость ядра NanoHttpd в ваш pom.xml :


    org.nanohttpd
    nanohttpd
    2.3.1

Чтобы создать простой сервер, нам нужно расширить NanoHttpd и переопределить его обслуживать метод:

public class App extends NanoHTTPD {
    public App() throws IOException {
        super(8080);
        start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
    }

    public static void main(String[] args ) throws IOException {
        new App();
    }

    @Override
    public Response serve(IHTTPSession session) {
        return newFixedLengthResponse("Hello world");
    }
}

Мы определили наш рабочий порт как 8080 и сервер для работы в качестве демона (без тайм-аута чтения).

Как только мы запустим приложение, URL-адрес http://localhost:8080/ вернет Привет, мир сообщение. Мы используем NanoHttpd#newFixedLengthResponse метод как удобный способ построения метод как удобный способ построения объект.

Давайте попробуем наш проект с cURL:

> curl 'http://localhost:8080/'
Hello world

3. REST API

На пути HTTP-методов NanoHttpd позволяет ПОЛУЧАТЬ, ПУБЛИКОВАТЬ, ПОМЕЩАТЬ, УДАЛЯТЬ, ВОЗГЛАВЛЯТЬ, ОТСЛЕЖИВАТЬ и некоторые другие.

Проще говоря, мы можем найти поддерживаемые HTTP-глаголы с помощью перечисления методов. Давайте посмотрим, как все обернется.

3.1. HTTP GET

Во-первых, давайте взглянем на GET. Скажем, например, что мы хотим возвращать контент только тогда, когда приложение получает запрос GET.

В отличие от контейнеров сервлетов Java , у нас нет метода doGet – вместо этого мы просто проверяем значение с помощью GetMethod :

@Override
public Response serve(IHTTPSession session) {
    if (session.getMethod() == Method.GET) {
        String itemIdRequestParameter = session.getParameters().get("itemId").get(0);
        return newFixedLengthResponse("Requested itemId = " + itemIdRequestParameter);
    }
    return newFixedLengthResponse(Response.Status.NOT_FOUND, MIME_PLAINTEXT, 
        "The requested resource does not exist");
}

Это было довольно просто, верно? Давайте проведем быстрый тест, свернув нашу новую конечную точку, и убедимся, что параметр запроса ItemId считан правильно:

> curl 'http://localhost:8080/?itemId=23Bk8'
Requested itemId = 23Bk8

3.2. HTTP-СООБЩЕНИЕ

Ранее мы реагировали на GET и считывали параметр из URL-адреса.

Чтобы охватить два самых популярных метода HTTP, нам пора обработать сообщение (и, таким образом, прочитать тело запроса):

@Override
public Response serve(IHTTPSession session) {
    if (session.getMethod() == Method.POST) {
        try {
            session.parseBody(new HashMap<>());
            String requestBody = session.getQueryParameterString();
            return newFixedLengthResponse("Request body = " + requestBody);
        } catch (IOException | ResponseException e) {
            // handle
        }
    }
    return newFixedLengthResponse(Response.Status.NOT_FOUND, MIME_PLAINTEXT, 
      "The requested resource does not exist");
}

Мы включим тело в нашу команду cURL :

> curl -X POST -d 'deliveryAddress=Washington nr 4&quantity=5''http://localhost:8080/'
Request body = deliveryAddress=Washington nr 4&quantity=5

Остальные методы HTTP очень похожи по своей природе, поэтому мы их пропустим.

4. Совместное использование Ресурсов Из Разных Источников

@Override 
public Response serve(IHTTPSession session) {
    Response response = newFixedLengthResponse("Hello world"); 
    response.addHeader("Access-Control-Allow-Origin", "*");
    return response;
}

Теперь, когда мы локон , мы вернем наш заголовок CORS:

> curl -v 'http://localhost:8080'
HTTP/1.1 200 OK 
Content-Type: text/html
Date: Thu, 13 Jun 2019 03:58:14 GMT
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Length: 11

Hello world

5. Загрузка файлов

NanoHttpd имеет отдельный зависимость для загрузки файлов , так что давайте добавим его в наш проект:


    org.nanohttpd
    nanohttpd-apache-fileupload
    2.3.1


    javax.servlet
    javax.servlet-api
    4.0.1
    provided

Обратите внимание, что servlet-api dependency также необходим (в противном случае мы получим ошибку компиляции).

То, что предоставляет NanoHttpd, – это класс, называемый Nano File Upload :

@Override
public Response serve(IHTTPSession session) {
    try {
        List files
          = new NanoFileUpload(new DiskFileItemFactory()).parseRequest(session);
        int uploadedCount = 0;
        for (FileItem file : files) {
            try {
                String fileName = file.getName(); 
                byte[] fileContent = file.get(); 
                Files.write(Paths.get(fileName), fileContent);
                uploadedCount++;
            } catch (Exception exception) {
                // handle
            }
        }
        return newFixedLengthResponse(Response.Status.OK, MIME_PLAINTEXT, 
          "Uploaded files " + uploadedCount + " out of " + files.size());
    } catch (IOException | FileUploadException e) {
        throw new IllegalArgumentException("Could not handle files from API request", e);
    }
    return newFixedLengthResponse(
      Response.Status.BAD_REQUEST, MIME_PLAINTEXT, "Error when uploading");
}

Эй, давай попробуем:

> curl -F '[email protected]/pathToFile.txt' 'http://localhost:8080'
Uploaded files: 1

6. Несколько Маршрутов

Во-первых, давайте добавим необходимую зависимость для nano lets :


    org.nanohttpd
    nanohttpd-nanolets
    2.3.1

А теперь мы расширим наш основной класс, используя RouterNanoHTTPD, определим наш рабочий порт и запустим сервер как демон.

Метод addmapping – это место, где мы определим наши обработчики:

public class MultipleRoutesExample extends RouterNanoHTTPD {
    public MultipleRoutesExample() throws IOException {
        super(8080);
        addMappings();
        start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
    }
 
    @Override
    public void addMappings() {
        // todo fill in the routes
    }
}

Следующий шаг – определить наши addmapping метод. Давайте определим несколько обработчиков.

Первый-это Обработчик индекса класса к пути”/”. Этот класс поставляется с библиотекой NanoHttpd и по умолчанию возвращает сообщение Hello World . Мы можем переопределить метод getText , когда нам нужен другой ответ:

addRoute("/", IndexHandler.class); // inside addMappings method

И чтобы проверить наш новый маршрут, мы можем сделать:

> curl 'http://localhost:8080' 

Hello world!

Во-вторых, давайте создадим новый Обработчик пользователя класс, который расширяет существующий Обработчик по умолчанию. Маршрут для него будет/ пользователи . Здесь мы поиграли с текстом, типом MIME и возвращенным кодом состояния:

public static class UserHandler extends DefaultHandler {
    @Override
    public String getText() {
        return "UserA, UserB, UserC";
    }

    @Override
    public String getMimeType() {
        return MIME_PLAINTEXT;
    }

    @Override
    public Response.IStatus getStatus() {
        return Response.Status.OK;
    }
}

Чтобы вызвать этот маршрут, мы снова выполним команду cURL :

> curl -X POST 'http://localhost:8080/users' 
UserA, UserB, UserC

Наконец, мы можем исследовать Общий обработчик с новым Обработчик хранилища класс. Мы изменили возвращенное сообщение, включив в него StoreID раздел URL-адреса.

public static class StoreHandler extends GeneralHandler {
    @Override
    public Response get(
      UriResource uriResource, Map urlParams, IHTTPSession session) {
        return newFixedLengthResponse("Retrieving store for id = "
          + urlParams.get("storeId"));
    }
}

Давайте проверим наш новый API:

> curl 'http://localhost:8080/stores/123' 
Retrieving store for id = 123

7. HTTPS

Чтобы использовать HTTPS, нам понадобится сертификат. Пожалуйста, обратитесь к нашей статье о SSL для получения более подробной информации.

Мы могли бы использовать такую службу, как Let’s Encrypt , или мы можем просто сгенерировать самозаверяющий сертификат следующим образом:

> keytool -genkey -keyalg RSA -alias selfsigned
  -keystore keystore.jks -storepass password -validity 360
  -keysize 2048 -ext SAN=DNS:localhost,IP:127.0.0.1  -validity 9999

Затем мы скопируем это keystore.jks в папку на нашем пути к классу, например, в папку src/main/resources проекта Maven.

После этого мы можем сослаться на него в вызове NanoHttpd#make SSLSocketFactory :

public class HttpsExample  extends NanoHTTPD {

    public HttpsExample() throws IOException {
        super(8080);
        makeSecure(NanoHTTPD.makeSSLSocketFactory(
          "/keystore.jks", "password".toCharArray()), null);
        start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
    }

    // main and serve methods
}

А теперь мы можем попробовать. Пожалуйста, обратите внимание на использование параметра — небезопасно , потому что cURL не сможет проверить наш самозаверяющий сертификат по умолчанию:

> curl --insecure 'https://localhost:8443'
HTTPS call is a success

8. WebSockets

NanoHttpd поддерживает WebSockets .

Давайте создадим простейшую реализацию WebSocket. Для этого нам нужно расширить класс Nanobsd . Нам также нужно будет добавить зависимость NanoHttpd для WebSocket:


    org.nanohttpd
    nanohttpd-websocket
    2.3.1

Для нашей реализации мы просто ответим простым текстом.:

public class WsdExample extends NanoWSD {
    public WsdExample() throws IOException {
        super(8080);
        start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
    }

    public static void main(String[] args) throws IOException {
        new WsdExample();
    }

    @Override
    protected WebSocket openWebSocket(IHTTPSession ihttpSession) {
        return new WsdSocket(ihttpSession);
    }

    private static class WsdSocket extends WebSocket {
        public WsdSocket(IHTTPSession handshakeRequest) {
            super(handshakeRequest);
        }

        //override onOpen, onClose, onPong and onException methods

        @Override
        protected void onMessage(WebSocketFrame webSocketFrame) {
            try {
                send(webSocketFrame.getTextPayload() + " to you");
            } catch (IOException e) {
                // handle
            }
        }
    }
}

Вместо cURL на этот раз мы будем использовать wscat :

> wscat -c localhost:8080
hello
hello to you
bye
bye to you

9. Заключение

Подводя итог, мы создали проект, который использует библиотеку NanoHttpd. Затем мы определили RESTful API и изучили больше функций, связанных с HTTP. В конце концов, мы также внедрили WebSocket.

Реализация всех этих фрагментов доступна на GitHub .