О принципе фреймворка 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 — это огромный фреймворк, который включает в себя слишком много вещей и включает в себя множество базовых знаний о компьютерных сетях. Спасибо!