О принципе фреймворка OkHttp

Анализ процесса

Начнем с простого HTTP-запроса:

Приведенный выше код инициирует два простых HTTP-запроса. Поток запросов показан на следующем рисунке.

На приведенной выше блок-схеме показана только цепочка ответственности. После предыдущего введения принцип цепочки ответственности и каждого перехватчика будет введен отдельно.

OkHttpClient

Мы используем новый OkHttpClient() для создания OkHttpClient по умолчанию, и мы также можем использовать OkHttpClient.Builder для создания клиента с пользовательскими параметрами.

Позже, когда мы будем использовать сетевые запросы, мы будем использовать этот клиент. Его можно понимать как основной класс всего OkHttp. Он инкапсулирует весь OkHttp и обеспечивает инициацию внешнего запроса и некоторые интерфейсы настройки параметров. Мы можем использовать OkHttpClient .Builder для установки, отвечающей за внутреннюю координацию работы каждого класса, на самом деле он не содержит слишком много кода.

Запрос

Request хорошо понимает и отвечает за сборку запроса.

Звонок (Реальный Звонок)

Затем вызовите метод client.newCall(request), что означает создание нового запроса для выполнения, и получите объект Call (реализованный как RealCall) через метод newCall. В настоящее время мы используем вызов call’а execute/enqueue, чтобы инициировать синхронизирующий/асинхронный запрос.

Таким образом, каждый Request в конечном итоге будет инкапсулирован в объект RealCall. RealCall и Request имеют однозначное соответствие. Вызов используется для описания запроса, который может быть выполнен и прерван. Мы создаем объект RealCall каждый раз, когда инициируем запрос.

Наконец, RealCall#getResponseWithInterceptorChain() вызывается для инициации запроса, и этот метод возвращает результат ответа Response.

Диспетчер

Dispatcher используется для управления всеми запросами соответствующего ему OkHttpClient. Как видно из приведенной выше блок-схемы, при использовании асинхронных запросов запрос будет делегирован объекту Dispatcher для обработки, а объект Dispatcher создается с созданием OkHttpClient.

На самом деле Dispatcher используется не только для управления асинхронными запросами, но и отвечает за управление синхронными запросами.

Когда мы инициируем запрос, будь то асинхронный или синхронный, он будет записан Dispatcher.

Мы можем получить объект Dispatcher через OkHtpClient#dispatcher() для унифицированного управления запросами, например, завершения всех запросов, получения пулов потоков и т. д.
Dispatcher содержит три очереди:

  • readyAsyncCalls: Сначала в очередь будет добавлен новый асинхронный запрос.
  • runningAsyncCalls: текущие асинхронные запросы
  • runningSyncCalls: текущие запросы на синхронизацию

Dispatcher содержит пул потоков по умолчанию для выполнения всех асинхронных запросов. Вы также можете указать пул потоков через конструктор, и все асинхронные запросы будут выполняться через этот пул потоков.

Как и синхронные запросы, асинхронные запросы в конечном итоге будут вызывать RealCall#getResponseWithInterceptorChain() для инициации запросов, но один вызывается напрямую, а другой вызывается в пуле потоков.

В приведенном выше введении было обнаружено, что ключевым моментом является то, что метод с длинным именем, пока он вызывается, может возвращать ответ, и этот метод начинает использовать известную модель цепи ответственности OkHttp. .

Цепочка ответственности OkHttp

Все начинается с метода с очень длинным названием. Мы знаем, что в любом случае будет вызван RealCall#getResponseWithInterceptorChain(), чтобы инициировать запрос и получить окончательный ответ.

Этот метод соберет список перехватчиков в соответствии с перехватчиком, установленным пользователем, и несколькими перехватчиками по умолчанию, а затем создаст цепочку ответственности.

После того, как цепочка ответственности создана, ее метод процесса будет вызываться для получения ответа и возврата его, что включает в себя два понятия: Interceptor и Chain.

Перехватчик

В качестве абстрактного понятия перехватчика интерфейс Interceptor разработан как единичный узел в цепочке ответственности для наблюдения, перехвата и обработки запросов, таких как добавление заголовков, перенаправление, обработка данных и т. д.

Перехватчики независимы друг от друга, и каждый Перехватчик отвечает только за те задачи, на которых он сосредоточен, и не контактирует с другими Перехватчиками.

Интерфейс Interceptor содержит только один метод (OkHttp теперь переписан на Kotlin):

Метод перехвата получает Chain в качестве параметра и возвращает ответ.

В RealCall к Chain ответственности будет добавлено следующее Interceptors по умолчанию для выполнения основных функций:

  • Установленный пользователем перехватчик
    RetryAndFollowUpInterceptor: повторная попытка и перенаправление в случае сбоя
  • BridgeInterceptor: обрабатывает сетевые заголовки, файлы cookie, gzip и т. д.
  • CacheInterceptor: управлять кешем
  • ConnectInterceptor: подключиться к серверу
  • Если это запрос WebSocket, добавьте соответствующие перехватчики.
  • CallServerInterceptor: отправка/прием данных

Конкретное значение и принцип действия этих Перехватчиков будут подробно описаны позже.

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

Благодаря обработке этих перехватчиков идеальный Response в конечном итоге будет возвращен методу с длинным именем в RealCall, а затем возвращен нижестоящим пользователям. На этом полный запрос подошёл к концу.

Цепь

Цепь используется для описания Цепочки Ответственности, через которую метод процесса начинает выполнять каждый узел в цепочке по очереди и возвращает обработанный Ответ.
Единственной реализацией Цепи является RealInterceptorChain (далее RIC). , RIC можно назвать цепочкой ответственности перехватчиков, а узлы в ней состоят из перехватчиков, добавленных в RealCall. Из-за независимости перехватчиков RIC также содержит некоторые общие параметры и общие объекты.

Interceptor и Chain зависят друг от друга, вызывают друг друга и развиваются вместе, образуя идеальную цепочку вызовов. Давайте посмотрим на их диаграмму взаимосвязи вызовов:

На приведенном выше рисунке хорошо видно, что когда мы вызываем метод Chain#process в Interceptor для получения Response, запрос будет обрабатываться в соответствии с Interceptor после вызова текущей позиции.

После завершения обработки Response будет возвращаться к текущему Interceptor, а затем после обработки возвращаться на верхний уровень до конца обхода.

Сетевое подключение и отправка и получение данных

Основные концепции, базовая конфигурация, управление потоками и цепочка ответственности OkHttp были представлены выше. Давайте поговорим о душе сетевой инфраструктуры: установлении сетевых запросов и отправке и получении данных.

Несколько разных перехватчиков, добавленных в RealCall, взаимодействуют друг с другом для выполнения этих функций. Пока вы понимаете эти основные перехватчики, вы понимаете душу OkHttp.

На самом деле, я не рекомендую уделять слишком много внимания деталям реализации при чтении исходного кода. Пока вы понимаете идеи дизайна, общая реализация почти одинакова, иначе легко запутаться в ответственных деталях.

Итак, прежде чем представить эти перехватчики, давайте познакомимся с некоторыми основными понятиями OkHttp.

Как устанавливается соединение

Многие из фреймворков сетевых запросов, которые мы видели ранее, такие как Volley и т. д., подключаются к серверу через HTTPURLConnection на нижнем уровне, и OkHttp лучше. Поскольку протокол HTTP основан на протоколе TCP/IP, а нижний уровень по-прежнему использует Socket, OkHttp напрямую использует Socket для выполнения HTTP-запросов.

Маршрут

route — это конкретный маршрут, используемый для подключения к серверу. Он содержит такие параметры, как IP-адрес, порт, прокси и т. д.
Один и тот же адрес интерфейса может соответствовать нескольким маршрутам из-за того, что прокси или DNS могут возвращать несколько IP-адресов.

Маршрут будет использоваться вместо IP-адреса непосредственно при создании соединения.

Выбор маршрута

Селектор маршрутов, который хранит все доступные маршруты и получает следующий маршрут с помощью метода RouteSelector#next, когда готов к подключению.

Стоит отметить, что RouteSelector содержит объект routeDatabase, в котором хранятся маршруты, которые не удалось подключить, а RouteSelector будет хранить последний маршрут, который не удалось подключить в конце, чтобы улучшить скорость соединения.

RealConnection

RealConnection реализует интерфейс Connection, который использует Socket для установления соединений HTTP/HTTPS и получения потоков ввода-вывода. Одно и то жеподключение может содержать несколько HTTP-запросов и ответов.

На самом деле его можно грубо понимать как инкапсуляцию сокета, потока ввода-вывода и некоторых протоколов. Это включает в себя множество знаний, связанных с компьютерными сетями, таких как рукопожатие TLS, проверка HTTPS и так далее.

RealConnectionPool

Это пул, используемый для хранения RealConnection, а внутри для хранения используется двусторонняя очередь.

В OkHttp соединение (RealConnection) не будет закрыто и освобождено сразу после его использования, а будет сохранено в пуле соединений (RealConnectionPool).

В дополнение к кэшированию подключений пул кэширования также отвечает за регулярную очистку соединений с истекшим сроком действия. Поле поддерживается в RealConnection для описания времени простоя соединения.

Каждый раз, когда новое соединение добавляется в пул соединений, выполняется обнаружение, просматривая все Connect, чтобы найти соединение, которое в настоящее время не используется и имеет самое продолжительное время простоя.

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

Кроме того, RealConnection также поддерживает список слабых ссылок Transmitter для хранения Transmitter, которые в настоящее время используют соединение. Когда список пуст, это означает, что соединение не используется.

ExchangeCodec

ExchangeCodec отвечает за кодирование и декодирование Response, то есть запись запроса и чтение ответа. Наши данные запроса и ответа читаются и записываются через него.

Итак, Connection отвечает за установление соединения, а ExchangeCodec отвечает за отправку и получение данных.

Существует два класса реализации интерфейса ExchangeCodec: Http1ExchangeCodec и Http2ExchangeCodec, соответствующие двум версиям протокола соответственно.

Обмен

Функция Exchange аналогична ExchangeCodec, но соответствует одному запросу, который отвечает за некоторые функции управления соединениями и распределения событий на основе ExchangeCodec.

В частности, Exchange соответствует запросу один за другим. Когда создается новый запрос, создается Exchange. Exchange отвечает за отправку запроса и чтение данных ответа, а ExchangeCodec используется для отправки и получения данных.

Передатчик

Transmitter — мост сетевого уровня OkHttp. Упомянутые выше концепции в конечном счете интегрируются через Transmitter и обеспечивают внешние функции.

Хорошо, теперь, когда основные понятия введены, давайте начнем рассматривать перехватчики.

RetryAndFollowUpInterceptor

Этот перехватчик, как следует из названия, отвечает за неудачные повторные попытки и перенаправления.
Условия, которые могут вызвать повторную попытку или перенаправление, следующие:

  • 401: Неавторизованный
  • 407: Прокси не авторизован
  • 503: Служба не авторизована
  • 3xx: запрос перенаправления
  • 408: время запроса истекло
  • И некоторые исключения ввода-вывода и другие сбои соединения

Как мы упоминали выше, из-за прокси-сервера и DNS для одного и того же URL-адреса может быть несколько IP-адресов. При подключении выберите соответствующий маршрут через RouteSelector для подключения, чтобы неудачная повторная попытка здесь не относилась к нескольким IP-адресам для одного и того же IP-адреса. Повторные попытки должны попробовать адреса в таблице маршрутизации один за другим.

Если код ответа 401 или 407, это означает, что запрос не прошел проверку подлинности. В это время запрос повторно аутентифицируется, а затем возвращается аутентифицированный запрос.

Код ответа 3xx означает перенаправление. В это время адрес перенаправления находится в поле Location заголовка ответа, а затем создается новый запрос на основе этого нового адреса и предыдущего запроса и возвращается.

Код ответа 503 указывает на ошибку сервера, но это временно и может быть восстановлено в ближайшее время, поэтому предыдущий запрос будет возвращен напрямую.

МостПерехватчик

BridgeInterceptor — это мост между пользователями и сетью, отвечающий за преобразование пользовательских запросов в сетевые запросы, то есть формирование сетевых заголовков и настройку данных ответа на основе информации запроса.

На самом деле, BridgeInterceptor отвечает за настройку файлов cookie и gzip.
Прежде чем начать сетевой запрос, BridgeInterceptor сначала определит, есть ли файл cookie через URL-адрес, и если да, он доставит файл cookie.

После того, как запрос завершится, он также определит, содержит ли заголовок ответа поле Set-Cookie, и оно будет сохранено при следующем использовании. Однако операция по хранению файлов cookie будет делегирована CookieJar.

OkHttp по умолчанию предоставляет пустой объект CookieJar, что означает, что никакая операция не выполняется по умолчанию, но вы можете указать свой собственный CookieJar для использования при создании OkHttp.

Если поля Accept-Encoding и Range не включены в заголовок запроса Request, к нему будет добавлен заголовок запроса Accept-Encoding: gzip. После получения данных ответа, если в ответе указано, что используется gzip, данные ответа будут переданы на декодирование okio GzipSource.

КэшПерехватчик

CacheInterceptor отвечает за кэширование данных ответа.
Этот метод сначала пытается получить кэшированные данные через объект Cache, а затем получает стратегию кэширования через CacheStrategy.

В результате расчета этой стратегии мы можем получить два обнуляемых объекта: networkRequest и cacheResponse.

Где networkRequest — исходный запрос, но может быть пустым. Пусто оно или нет, контролируется CacheStrategy.

cacheResponse — это Response, полученное с помощью Cache, как и выше, и также может быть пустым.
Затем вы можете обработать кеш, оценив обнуляемость двух объектов, логика следующая:

  • Если оба пусты, это означает, что не разрешено ни использование сетевых запросов, ни использование кэшей, ни кэш-промахов, и возвращается ошибка 504 напрямую.
  • Если пусто только networkRequest, это означает, что сетевой запрос запрещен, и сразу возвращается попадание Response из кеша.
  • Если ни один из них не пуст, начните делать запросы и получать данные ответов.
  • Если cacheResponse в это время не пусто и код ответа равен 304, cacheResponse возвращается напрямую, а кэш обновляется данными ответа.
  • Если cacheResponse пусто, данные ответа будут сохранены в Cache.
    Вернуть данные ответа.

Следует отметить, что упомянутый выше объект Cache по умолчанию пуст. Если он пуст, операции, связанные с ним, выполняться не будут, и cacheResponse должен быть пустым.

Мы можем установить Cache в OkHttpClient.

ConnectInterceptor

ConnectInterceptor используется для открытия соединения с сервером.
Код очень прост. Он создаст объект Exchange с помощью метода Transmitter#newExchange и вызовет метод Chain#process.

В методе newExchange он сначала попытается найти существующее соединение в диапазоне от RealConnectionPool до ExchangeFinder.

Если он не найден, он заново создаст файл RealConnection и запустит соединение, а затем сохранит его в файле RealConnectionPool.

В это время объект RealConnection был подготовлен, а затем создан с помощью протокола запроса Различного ExchangeCodec и возврата. Конкретные детали были упомянуты выше и не будут здесь подробно описываться.

После создания ExchangeCodec с помощью описанных выше шагов создайте объект Exchange на его основе и других параметрах и верните его.

ConnectInterceptor вызывает метод Chain#process с объектом Exchange, возвращенным методом newExchange, в качестве параметра.

CallServerInterceptor

CallServerInterceptor отвечает за чтение и запись данных.
Это последний перехватчик. Все, что должно быть подготовлено здесь, готово. Через него данные в Request будут отправлены на сервер, а полученные данные будут записаны в Response.

Таким образом, исходный код OkHttp здесь практически проанализирован. На самом деле, есть много вещей, которые не были упомянуты. OkHttp — это огромный фреймворк, который включает в себя слишком много вещей и включает в себя множество базовых знаний о компьютерных сетях. Спасибо!