Netty реализует высокопроизводительный HTTP-сервер

Java сервер HTTP Netty


Говоря о методе HTTP

Чтобы реализовать HTTP-сервер (или клиент) с помощью netty, вы должны сначала понять протокол HTTP.

HTTP используется в вычислительной модели клиент-сервер какответ на запроспротокол.

Например, веб-браузер может быть клиентом, а приложение, работающее на компьютере, на котором размещен веб-сайт, может быть сервером. Клиент отправляет сообщение HTTP-запроса на сервер.

Сервер предоставляет ресурсы, такие как HTML-файлы и другой контент, или выполняет другие функции от имени клиента, возвращая клиенту ответное сообщение. Ответ содержит информацию о статусе завершения запроса, а также может содержать запрошенное содержимое в теле сообщения.

Что такое метод HTTP?

Любой, кто писал веб-формы, должен быть знаком с GET и POST, но вы знаете, что такоеGETиPOSTНе так ли!? Сегодняшние инструменты веб-дизайна достаточно развиты, и вам даже не нужно прикасаться к синтаксису HTML, чтобы завершить масштабный веб-сайт. Постепенно многие люди забывают принцип реализации нижнего уровня HTTP, что приводит к сбоям в работе. в случае ошибок Отлаживайте корректно.

В первые дни при написании синтаксиса HTML-форм были написаны следующие методы записи, но большинство разработчиков программного обеспечения использовали POST для передачи формы.

<form action="" method="POST/GET">
</form>

Однако, чтобы получить переменные формы в нашей веб-программе, нам нужно только вызвать методы, уже инкапсулированные системой, такие как PHP, используя $_REQUEST, JAVA, используя getParameter(), ASP, используя Request.Form() и так далее. . Из приведенного выше метода кажется, что использование POST или GET не очень важно. Многие веб-инженеры помнят использование методов форм, таких как «POST может передавать больше данных», «Использовать POST при отправке файлов в форме», «POST безопаснее, чем GET» и другие странные концепции.

На самом деле, есть разница между использованием POST или GET.Давайте сначала объясним метод HTTP.В версии HTTP 1.1 определены восемь методов, а именно:

  • OPTIONS

  • GET

  • HEAD

  • POST

  • PUT

  • DELETE

  • TRACE

  • CONNECT

МОЙ БОГ! Эти методы кажутся незнакомыми. Форма, которую мы используем, использует только два из этих методов (GET/POST), а другие методы используются редко, но в архитектуре дизайна RESTful будет использоваться больше методов для упрощения дизайна.

GET- и POST-методы

В качестве примера, если бы HTTP представлял собой механизм отправки писем в нашей реальной жизни сегодня.

:speaker: Тогда формат конверта HTTP. Назовем содержимое вне конверта http-заголовком, а письмо внутри конверта телом сообщения, тогда HTTP-метод — это правила доставки письма, которые нужно сообщить почтальону.

Предположим, GET указывает, что конверт не должен содержать букв.Так же, как и на открытке, вы можете писать информацию для доставки на конверте (http-header), пока он не будет заполнен, и цена не будет дешевле. Однако POST - это метод отправки писем с конвертами (конверты имеют содержимое).Конверты могут быть не только написаны, но информация или файлы, которые вы хотите отправить, также могут быть помещены в конверт (тело сообщения), что более важно. дорогой.

При использовании GET мы напрямую добавляем данные для отправки на адрес (URL), который мы хотим отправить, с помощью строки запроса (метод кодирования Key/Vaule), а затем отправляем их почтальону.

При использовании POST адрес доставки (URL) пишется на конверте, а отправляемая информация пишется на другом листе почтовой бумаги.

ПОЛУЧИТЬ метод

Далее, позвольте мне представить фактическую операцию:

Давайте сначала посмотрим, как GET передает данные.Когда мы отправляем форму GET, следующий пример:

<form method="get" action="">
<input type="text" name="id" />
<input type="submit" />
</form>

Когда форма отправляется, URL-адрес браузера становится "xxx.toright.com/?id=010101", браузер автоматически преобразует содержимое формы в строку запроса и добавит его к URL-адресу для подключения.

На этом этапе давайте взглянем на содержимое пакета HTTP-запроса:

GET /?id=010101 HTTP/1.1
Host: xxx.toright.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-TW; rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13 GTB7.1 ( .NET CLR 3.5.30729)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-tw,en-us;q=0.7,en;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: UTF-8,*
Keep-Alive: 115
Connection: keep-alive

В методе HTTP GET не разрешено передавать данные в теле сообщения, потому что это GET, что означает получение данных.

В адресной строке браузера вы можете увидеть информацию, которая будет отправлена ​​​​нашей формой.Если вы хотите отправить пароль, не будет ли это «с первого взгляда» ... Это проблема безопасности, о которой все часто упоминают.

POST-метод

Давайте посмотрим на данные передачи POST

<form method="post" action="">
<input type="text" name="id" />
<input type="submit" />
</form>

Столбец URL не изменился, поэтому давайте посмотрим на содержимое пакета HTTP-запроса:

POST / HTTP/1.1
Host: xxx.toright.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-TW; rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13 GTB7.1 ( .NET CLR 3.5.30729)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-tw,en-us;q=0.7,en;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: UTF-8,*
Keep-Alive: 115
Connection: keep-alive
 
Content-Type: application/x-www-form-urlencoded
</code><code>Content-Length: 9
id=020202

Понять, почему? Оказывается, POST должен отправлять данные формы в теле сообщения, что кажется более безопасным, не заглядывая в пакет......:yellow_heart: . Кроме того, при отправке файлов будет использоваться многокомпонентное кодирование, и файлы будут отправляться вместе с другими полями формы в теле сообщения. В этом разница между GET и POST для отправки формы.

HTTP-кодек Netty

Чтобы обрабатывать HTTP-запросы через Netty, вам нужно сначала кодировать и декодировать.

Кодек NettyHTTP

public class HttpHelloWorldServerInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    public void initChannel(SocketChannel ch) {
        ChannelPipeline p = ch.pipeline();
        /**
         * 或者使用HttpRequestDecoder & HttpResponseEncoder
         */
        p.addLast(new HttpServerCodec());
        /**
         * 在处理POST消息体时需要加上
         */
        p.addLast(new HttpObjectAggregator(1024*1024));
        p.addLast(new HttpServerExpectContinueHandler());
        p.addLast(new HttpHelloWorldServerHandler());
    }
}
  • Строка 8. Вызовите метод **#new HttpServerCodec()**. Кодек поддерживает частичный анализ HTTP-запроса. Например, параметры, передаваемые HTTP-запросом GET, включаются в uri, поэтому параметры запроса можно анализировать с помощью HttpRequest. .
    • HttpRequestDecoderТо есть декодировать ByteBuf в HttpRequest и HttpContent.
    • HttpResponseEncoderТо есть закодируйте HttpResponse или HttpContent в ByteBuf.
    • HttpServerCodecТо есть комбинация HttpRequestDecoder и HttpResponseEncoder.

Однако для запросов HTTP POST информация о параметрах помещается в тело сообщения (что соответствует HttpMessage для netty), поэтому приведенные выше кодеки не могут полностью анализировать запросы HTTP POST.

Что делать в этой ситуации? Не паникуйте, netty предоставляет обработчик для этого.

  • Строка 12: вызовите метод **#new HttpObjectAggregator(1024*1024)**, который может объединять HttpMessage и HttpContent в FullHttpRequest или FullHttpResponse (в зависимости от того, обрабатывается ли запрос или ответ), а также может помочь вам в декодировании. Игнорируется, является ли это "блочным" режимом передачи.

    Поэтому при разборе HTTP-запросов POST обязательно включите HttpObjectAggregator в ChannelPipeline. (Пожалуйста, обратитесь к коду для получения подробной информации)

  • Строка 13: Функция этого метода: http 100-continue используется клиентом для консультации с сервером перед отправкой данных POST на сервер, чтобы узнать, обрабатывает ли сервер данные POST.Если нет, клиент не будет загружать данные. Данные POST. Если они обработаны, загружаются данные POST. В реальных приложениях протокол 100-continue используется только при отправке больших данных.

Реализация ответных сообщений HTTP

Процесс инкапсуляции объектов Java в пакеты двоичных данных в соответствии с протоколом HTTP называется кодированием, а процесс разбора объектов Java из пакетов двоичных данных называется декодированием. Прежде чем научиться использовать Netty для кодирования и декодирования протокола HTTP, мы сначала определить Давайте посмотрим на объекты Java, которые клиент взаимодействует с сервером.

Java-объект

Мы определяем объекты Java в процессе связи следующим образом.

@Data
public class User {
    private String userName;

    private String method;

    private Date date;
}
  1. Выше приведен абстрактный класс Java-объектов в процессе связи.Как видите, мы определили имя пользователя (значение по умолчанию — sanshengshui ), метод http-запроса и текущее время и дату.
  2. @DataАннотированоlombokПри условии, что это автоматически поможет нам создать методы получения/установки, сократив количество повторяющегося кода, рекомендуется использовать

После определения объекта Java нам необходимо определить правило преобразования объекта Java в двоичные данные Это правило называется сериализацией объектов Java.

Сериализация

Мы определяем интерфейс сериализации следующим образом

/**
 * 序列化接口类
 */
public interface Serializer {
    /**
     * java 对象转换成二进制
     */
    byte[] serialize(Object object);

    /**
     * 二进制转换成 java 对象
     */
    <T> T deserialize(Class<T> clazz, byte[] bytes);
}

Интерфейс сериализации имеет два метода: serialize() преобразует объект Java в массив байтов, а deserialize() преобразует массив байтов в определенный тип объекта Java.fastjsonв качестве основы сериализации.

public class JSONSerializer implements Serializer {
    @Override
    public byte[] serialize(Object object) {
        return JSON.toJSONBytes(object);
    }

    @Override
    public <T> T deserialize(Class<T> clazz, byte[] bytes) {
        return JSON.parseObject(bytes,clazz);
    }
}

кодирование

		User user = new User();
        user.setUserName("sanshengshui");
        user.setDate(new Date());
        user.setMethod("get");
        JSONSerializer jsonSerializer = new JSONSerializer();
        //将Java对象序列化成为二级制数据包
        byte[] content = jsonSerializer.serialize(user);
        FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, Unpooled.wrappedBuffer(content));
        response.headers().set(CONTENT_TYPE, "text/plain");
        response.headers().setInt(CONTENT_LENGTH, response.content().readableBytes());

        boolean keepAlive = HttpUtil.isKeepAlive(request);
        if (!keepAlive) {
            ctx.write(response).addListener(ChannelFutureListener.CLOSE);
           } else {
            response.headers().set(CONNECTION, KEEP_ALIVE);
            ctx.write(response);
           }

Практика парсинга HTTP GET

Как упоминалось выше, параметры HTTP-запроса GET содержатся в uri, и uri можно разобрать следующими способами:

HttpRequest request = (HttpRequest) msg;
String uri = request.uri();

Важно отметить, что при использовании браузера для инициации HTTP-запроса ему часто мешает uri="/favicon.ico", поэтому лучше всего обрабатывать его специально:

if(uri.equals(FAVICON_ICO)){
    return;
}

Следующим шагом является разбор uri. Здесь нужно использоватьQueryStringDecoder:

Splits an HTTP query string into a path string and key-value parameter pairs.
This decoder is for one time use only.  Create a new instance for each URI:
 
QueryStringDecoder decoder = new QueryStringDecoder("/hello?recipient=world&x=1;y=2");
assert decoder.getPath().equals("/hello");
assert decoder.getParameters().get("recipient").get(0).equals("world");
assert decoder.getParameters().get("x").get(0).equals("1");
assert decoder.getParameters().get("y").get(0).equals("2");
 
This decoder can also decode the content of an HTTP POST request whose
content type is application/x-www-form-urlencoded:
 
QueryStringDecoder decoder = new QueryStringDecoder("recipient=world&x=1;y=2", false);

Как видно из приведенного выше описания, функция QueryStringDecoder состоит в том, чтобы разделить HTTP uri на пары параметров пути и значения, а также может использоваться для декодирования HTTP POST с Content-Type = "application/x-www-form -urlencoded". Обратите внимание, что этот декодер можно использовать только один раз.

Код разбора выглядит следующим образом:

String uri = request.uri();
HttpMethod method = request.method();
if(method.equals(HttpMethod.GET)){
&emsp;&emsp;QueryStringDecoder queryDecoder = new QueryStringDecoder(uri, Charsets.toCharset(CharEncoding.UTF_8));
&emsp;&emsp;Map<String, List<String>> uriAttributes = queryDecoder.parameters();
&emsp;&emsp;//此处仅打印请求参数(你可以根据业务需求自定义处理)
&emsp;&emsp;for (Map.Entry<String, List<String>> attr : uriAttributes.entrySet()) {
&emsp;&emsp;&emsp;&emsp;for (String attrVal : attr.getValue()) {
&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;System.out.println(attr.getKey() + "=" + attrVal);
&emsp;&emsp;&emsp;&emsp;}
&emsp;&emsp;}
}

Практика парсинга HTTP POST

Как упоминалось ранее, для анализа тела сообщения HTTP-запроса POST обязательно используйте HttpObjectAggregator. Но нужно ли конвертировать msg в FullHttpRequest? Ответ нет, и смотрите ниже.

Сначала объясните, что такое FullHttpRequest:

Combinate the HttpRequest and FullHttpMessage, so the request is a complete HTTP request.

То есть FullHttpRequest содержит HttpRequest и FullHttpMessage, которые представляют собой полное тело HTTP-запроса.

И способ конвертировать msg в FullHttpRequest очень прост:

FullHttpRequest fullRequest = (FullHttpRequest) msg;

Следующим шагом будет его анализ в нескольких Content-Types.

private void dealWithContentType() throws Exception{
        String contentType = getContentType();
        //可以使用HttpJsonDecoder
        if(contentType.equals("application/json")){
            String jsonStr = fullRequest.content().toString(Charsets.toCharset(CharEncoding.UTF_8));
            JSONObject obj = JSON.parseObject(jsonStr);
            for(Map.Entry<String, Object> item : obj.entrySet()){
                logger.info(item.getKey()+"="+item.getValue().toString());
            }

        }else if(contentType.equals("application/x-www-form-urlencoded")){
            //方式一:使用 QueryStringDecoder
			String jsonStr = fullRequest.content().toString(Charsets.toCharset(CharEncoding.UTF_8));
			QueryStringDecoder queryDecoder = new QueryStringDecoder(jsonStr, false);
			Map<String, List<String>> uriAttributes = queryDecoder.parameters();
            for (Map.Entry<String, List<String>> attr : uriAttributes.entrySet()) {
                for (String attrVal : attr.getValue()) {
                    logger.info(attr.getKey() + "=" + attrVal);
                }
            }

        }else if(contentType.equals("multipart/form-data")){
            //TODO 用于文件上传
        }else{
            //do nothing...
        }
    }
    private String getContentType(){
        String typeStr = headers.get("Content-Type").toString();
        String[] list = typeStr.split(";");
        return list[0];
    }

функциональный тест

я используюPostmanСделайте запрос на http-сервер, реализованный netty.Если вы чувствуете, что можете, вы можете загрузить его самостоятельно.

Получить запрос

Postman:

Server:

16:58:59.130 [nioEventLoopGroup-3-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.maxSharedCapacityFactor: 2
16:58:59.130 [nioEventLoopGroup-3-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.linkCapacity: 16
16:58:59.130 [nioEventLoopGroup-3-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.ratio: 8
//打印请求url
16:58:59.159 [nioEventLoopGroup-3-1] INFO com.sanshengshui.netty.HttpHelloWorldServerHandler - http uri: /

Отправить запрос

Postman:

Server:

16:58:59.130 [nioEventLoopGroup-3-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.maxSharedCapacityFactor: 2
16:58:59.130 [nioEventLoopGroup-3-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.linkCapacity: 16
16:58:59.130 [nioEventLoopGroup-3-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.ratio: 8
16:58:59.159 [nioEventLoopGroup-3-1] INFO com.sanshengshui.netty.HttpHelloWorldServerHandler - http uri: /
17:03:59.813 [nioEventLoopGroup-2-1] INFO io.netty.handler.logging.LoggingHandler - [id: 0x0f3f5fdd, L:/0:0:0:0:0:0:0:0:8888] READ: [id: 0xfd00cb1b, L:/0:0:0:0:0:0:0:1:8888 - R:/0:0:0:0:0:0:0:1:45768]
17:03:59.813 [nioEventLoopGroup-2-1] INFO io.netty.handler.logging.LoggingHandler - [id: 0x0f3f5fdd, L:/0:0:0:0:0:0:0:0:8888] READ COMPLETE
//打印post请求的url
17:03:59.817 [nioEventLoopGroup-3-2] INFO com.sanshengshui.netty.HttpHelloWorldServerHandler - http uri: /ttt

Гатлинг производительность, нагрузочное тестирование

Если вы не знакомы с инструментами тестирования Гатлинга, вы можете взглянуть на мою предыдущую статью:

  1. Нагрузочный инструмент для тестирования производительности - Gatling

  2. Gatling простой тестовый проект SpringBoot

Отчет о тестировании производительности выглядит примерно так:


================================================================================
---- Global Information --------------------------------------------------------
> request count                                    1178179 (OK=1178179 KO=0     )
> min response time                                      0 (OK=0      KO=-     )
> max response time                                  12547 (OK=12547  KO=-     )
> mean response time                                     1 (OK=1      KO=-     )
> std deviation                                         32 (OK=32     KO=-     )
> response time 50th percentile                          0 (OK=0      KO=-     )
> response time 75th percentile                          1 (OK=1      KO=-     )
> response time 95th percentile                          2 (OK=2      KO=-     )
> response time 99th percentile                          5 (OK=5      KO=-     )
> mean requests/sec                                10808.982 (OK=10808.982 KO=-     )
---- Response Time Distribution ------------------------------------------------
> t < 800 ms                                       1178139 (100%)
> 800 ms < t < 1200 ms                                   0 (  0%)
> t > 1200 ms                                           40 (  0%)
> failed                                                 0 (  0%)
================================================================================

разное

На этом подробное объяснение высокопроизводительного HTTP-сервера Netty заканчивается.

Netty реализует высокопроизводительный HTTP-сервер Адрес проекта проекта:GitHub.com/Саншэншу…

Оригинал не просто, если вы чувствуете себя хорошо, я надеюсь дать рекомендацию! Ваша поддержка - самая большая мотивация для моего письма!

Уведомление об авторских правах:

Автор: Му Шувэй

Источник блога сада:блог woo woo woo.cn на.com/Sanshenghu…

источник на гитхабе:GitHub.com/Саншэншу…    

Источник личного блога:sanshengshui.github.io/