Подробное объяснение http-пакетов (2) — как веб-контейнер анализирует http-пакеты

HTTP

Резюме

существуетПодробное http-сообщениеВ этой статье подробно представлена ​​текстовая структура http-сообщения. Итак, как сервер, как веб-контейнер анализирует http-сообщение? В этой статье для анализа того, как веб-контейнеры обрабатывают HTTP-пакеты, в качестве примеров используются контейнеры причала и подводного плавания.

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

start-line: 起始行,描述请求或响应的基本信息

*( header-field CRLF ): 头

CRLF

[message-body]: 消息body,实际传输的数据

jetty

Следующий код является версией jetty9.4.12.

Как парсить такую ​​длинную строку, джетти реализует это через конечный автомат. В частности, вы можете увидетьorg.eclipse.jetty.http.HttpParseсвоего рода

 public enum State
    {
        START,
        METHOD,
        
![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/10/9/16db0a55c99520eb~tplv-t2oaga2asx-image.image),
        SPACE1,
        STATUS,
        URI,
        SPACE2,
        REQUEST_VERSION,
        REASON,
        PROXY,
        HEADER,
        CONTENT,
        EOF_CONTENT,
        CHUNKED_CONTENT,
        CHUNK_SIZE,
        CHUNK_PARAMS,
        CHUNK,
        TRAILER,
        END,
        CLOSE,  // The associated stream/endpoint should be closed
        CLOSED  // The associated stream/endpoint is at EOF
    }

Всего он разделен на 21 штат, а затем перетекает между штатами. существуетparseNextВ методе стартовая строка -> заголовок -> содержимое тела анализируется отдельно

public boolean parseNext(ByteBuffer buffer)
    {
        try
        {
            // Start a request/response
            if (_state==State.START)
            {
                // 快速判断
                if (quickStart(buffer))
                    return true;
            }

            // Request/response line 转换
            if (_state.ordinal()>= State.START.ordinal() && _state.ordinal()<State.HEADER.ordinal())
            {
                if (parseLine(buffer))
                    return true;
            }

            // headers转换
            if (_state== State.HEADER)
            {
                if (parseFields(buffer))
                    return true;
            }

            // content转换
            if (_state.ordinal()>= State.CONTENT.ordinal() && _state.ordinal()<State.TRAILER.ordinal())
            {
                // Handle HEAD response
                if (_responseStatus>0 && _headResponse)
                {
                    setState(State.END);
                    return handleContentMessage();
                }
                else
                {
                    if (parseContent(buffer))
                        return true;
                }
            }
         
        return false;
    }

Общий процесс

Есть три пути

  1. начало -> начальная строка -> заголовок -> конец
  2. начало -> начальная строка -> заголовок -> содержимое -> конец
  3. начало -> начальная строка -> заголовок -> фрагмент содержимого -> конец

стартовая линия

стартовая строка = строка запроса (строка начала запроса) / (строка начала ответа) строка состояния

  1. Запрос смены состояния анализа пакета Строка запроса: START -> METHOD -> SPACE1 -> URI -> SPACE2 -> REQUEST_VERSION

  2. Переход состояния анализа пакета ответа Строка ответа: START -> RESPONSE_VERSION -> SPACE1 -> STATUS -> SPACE2 -> REASON

заголовок

Состояние HEADER только одно, и оно тоже различается в старой версии причала.HEADER_IN_NAM, HEADER_VALUE, HEADER_IN_VALUEПодождите, его удалили в 9.4. Чтобы повысить эффективность сопоставления, причал использует дерево Trie для быстрого сопоставления заголовка заголовка.

static
    {
        CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE));
        CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.KEEP_ALIVE));
      // 以下省略了很多了通用header头

content

Тело запроса:

  1. CONTENT -> END, это обычное сообщение с заголовком Content-Length. HttpParser продолжает работать в состоянии CONTENT до тех пор, пока ContentLength не достигнет указанного числа, а затем переходит в состояние END
  2. Передаваемые фрагментированные данные CHUNKED_CONTENT -> CHUNK_SIZE -> CHUNK -> CHUNK_END -> END

undertow

Undertow — это еще один веб-контейнер, чем он отличается от причала? Типы автоматов разные.io.undertow.util.HttpString.ParseState

    public static final int VERB = 0;
    public static final int PATH = 1;
    public static final int PATH_PARAMETERS = 2;
    public static final int QUERY_PARAMETERS = 3;
    public static final int VERSION = 4;
    public static final int AFTER_VERSION = 5;
    public static final int HEADER = 6;
    public static final int HEADER_VALUE = 7;
    public static final int PARSE_COMPLETE = 8;

Конкретный поток обработки находится вHttpRequestParserв абстрактном классе

public void handle(ByteBuffer buffer, final ParseState currentState, final HttpServerExchange builder) throws BadRequestException {
        if (currentState.state == ParseState.VERB) {
            //fast path, we assume that it will parse fully so we avoid all the if statements

            // 快速处理GET
            final int position = buffer.position();
            if (buffer.remaining() > 3
                    && buffer.get(position) == 'G'
                    && buffer.get(position + 1) == 'E'
                    && buffer.get(position + 2) == 'T'
                    && buffer.get(position + 3) == ' ') {
                buffer.position(position + 4);
                builder.setRequestMethod(Methods.GET);
                currentState.state = ParseState.PATH;
            } else {
                try {
                    handleHttpVerb(buffer, currentState, builder);
                } catch (IllegalArgumentException e) {
                    throw new BadRequestException(e);
                }
            }
			// 处理path
            handlePath(buffer, currentState, builder);
           // 处理版本
            if (failed) {
                handleHttpVersion(buffer, currentState, builder);
                handleAfterVersion(buffer, currentState);
            }
			// 处理header
            while (currentState.state != ParseState.PARSE_COMPLETE && buffer.hasRemaining()) {
                handleHeader(buffer, currentState, builder);
                if (currentState.state == ParseState.HEADER_VALUE) {
                    handleHeaderValue(buffer, currentState, builder);
                }
            }
            return;
        }
        handleStateful(buffer, currentState, builder);
    }

Отличие от причала заключается в обработке содержимого: после обработки заголовка данные помещаются вio.undertow.server.HttpServerExchange, а затем в зависимости от типа существуют различные методы чтения контента, такие как обработка фиксированной длины,FixedLengthStreamSourceConduit.

Обратите внимание на паблик-аккаунт [Abbot's Temple], как можно скорее получите обновление статьи и начните путь технической практики вместе с настоятелем

在这里插入图片描述

Ссылаться на

Woohoo.блог Java.net/D Levin/Arch…

Woohoo. Использовать с 0.com/2018/10/06/…

Высокое название .com/HTTP-внезапно страдает…

undertow.IO/undertow-делать…