Введение
Если вы разрабатываете под Android, то наверняка сталкивались с тем, что открываете вы своё приложение, которое отлично работало несколько лет, и тут внезапно оказывается, что Apache httpComponents стали deprecated, и их не рекомендуется использовать. Сначала давайте разберём, что же произошло, а потом сделаем выводы, что делать.
Что произошло
Слишком далеко закапываться я не стал, однако много интересных вещей можно получить из рассылки org.apache.hc.dev и джиры Apache. Например, факт что:
Android включал в себя старый pre-beta релиз библиотеки
Более того, на протяжении всей истории версия библиотеки не менялась, и если включаете у себя apache legacy, то это всё та же pre-beta.
Подтверждение можно прочитать тут.
Google просто забил на проблему
Разработчики из Apache активно работали над библиотекой, но назначенные им менеджеры Google не отвечали месяцами, менялись, а в конце концов сказали, что это не приоритетная задача, пользователям хватает старой версии, да и вообще надо выкинуть эту библиотеку. В подтверждение — немного из упомянутого мной листа рассылки (работа началась в 2008 году, а в 2010 внезапно проявился новый менеджер):
I'm Bob's successor on the Android team. If you've got questions about our use of the HTTP client code, I'll do my best to answer 'em. I regret that we haven't given this code much attention lately; that said I'm happy that it hasn't really needed it.
Тогда же прозвенел первый тревожный звоночек:
Because of the strict compatibility requirements of our platform, we will be unable to make forwards-incompatible API changes to the HTTP code. Unlike your desktop and serverside users, including the API as a part of the core platform significantly reduces our ability to make API changes.
These days we aren't spending much time on the HTTP client code. Our users seem to be mostly satisfied with the ancient version in the SDK, and have been directing their complaints elsewhere (crypto, locales, XML...).
That said, we do want to figure out a long term strategy for an HTTP client API that will work for both us and the Apache HTTP client team. We're considering a variety of options…
— Discouraging our users from using the built-in Apache HTTP client, preferring the JDK's own URLConnection classes. Whether this is feasible depends mostly on whether the new APIs in Java 6 (which we're working on) will satisfy the use cases that Apache HTTP client has covered for years.
— Replacing the Apache HTTP client API with a 3rd API in a «com.google» or «android.» package. Such an API would likely be based on parts of your own code, but with a more limited API.
— Tidying up the version of Apache HTTP that we're already stuck with. This includes deprecating APIs that shouldn't have ever been exposed as public, and possibly filling in any gaps using newer code from your project.
But none of this work is particularly urgent since we're actively fighting fires in other areas of the core libraries.
Они не могут производит изменения в коде существующей библиотеки из-за несовместимостей? Oh rly? А если вынесут это в свой отдельный пакет на основе кода Apache, то внезапно смогут? Вообще, breaking changes в Android это отдельная тема, выходящая за рамки данной статьи, но чего стоит только ограничение разрешений приложений в шестёрке… При этом ребята из Apache активно старались предоставить максимальную совместимость, и было готовы сделать для этого что угодно. Но увы.
Финал
А дальше библиотеку просто без всяких адекватных объяснений выпилили с официальной рекомендацией использовать HttpUrlConnection. Вот хотя бы что-то, отдалённо похожее на объяснение ситуации от Джесси Вилсона, который был на то время в Dalvik team (к слову, он же и был вторым менеджером Google по связям с Apache. Запомните это имя).
В его статье вы можете увидеть, что среди преимуществ он видит:
- Кеширование (которое, если надо, реализуется где угодно)
- Малый вес библиотеки (да, но сомнительный аргумент)
- Компрессия, которая сохраняет батарейку и ускоряет загрузку (да, но при желании мы можем использовать Google Compression Proxy и через Apache HttpComponents как обычный прокси)
Крайне сомнительные объяснения. Если вы сейчас сидите в шапочке из фольги, то рациональным доводом будет то, что гугл таким образом решил втихую заставить пользователей гонять весь свой трафик через свой прокси…
UPD. Как мне справедливо указали, под компрессией имеется в виду не использование GCP, а использование стандартного gzip… Который у Apache есть уже много-много лет. Так что тем более странно.
К сожалению, большинство разработчиков слепо верят Google и сразу считают, что библиотека Apache “плохая”, и нужно бежать выкидывать её из своего кода.
Разработчики из Apache прокомментировали это кратко:
Google has used the project when it suited their goals and screwed us afterwards. There is nothing we can do about it.
Эпилог c OkHttp
Если вы разрабатываете под Android, то наверняка видели рекомендации заменять Apache HttpComponents ныне популярной библиотекой OkHttp от Square.
А вы всё ещё помните милого парня Джесси Вилсона из Dalvik team? А вы знаете, что сейчас он работает в Square? И именно он является создателем OkHttp? Более того, вы знате, что OkHttp начинался как форк куска AOSP (Android Open Source Project), который в свою очередь брал свой код из Apache Harmony?
Так что это и есть по сути создание форка Apache с последующим выкидыванием оригинала из обращения (второй вариант из озвученных ранее Джесси в общении с Apache). Звучит довольно гнусно, не правда ли? Единственное что непонятно — была ли это инициатива Google или самого Джесси. Но поступил он крайне некрасиво, выкинув конкурентов с помощью Google и придя весь в белом со своим решением.
И что же?
Давайте разберёмся, какие есть варианты того, как жить дальше.
1. Использовать HttpUrlConnection
Рекомендованный подход Google. Действительно имеет смысл, если у вас простое приложение. Но не дай бог вам попробовать сделать что-то не совсем тривиальное. В моей практике таких случая было два — когда я пытался использовать SSL прокси и когда хотел обратиться к некоему айпишнику со своим именем хоста. Обе задачи при помощи HttpUrlConnection реализовать невозможно.
2. Использовать другие продвинутые альтернативы
Например, тот же OkHttp. Сам его не пробовал, но говорят, что библиотека хорошая. Однако, вы потратите много времени на переписывание хорошего готового имеющегося кода (если у вас уже есть приложение). Ну и касательно именно OkHttp — я бы не стал использовать столь неприятно пахнущий форк.
3. Использовать legacy библиотеку
Да, вы можете продолжать использовать всю ту же древнюю бету, что и раньше. Но зачем? Как быстрое затыкание дырки это решение неплохое, а если нет… То конечно нет. Обидно, что именно такой ответ является наиболее популярным решением на том же stack overflow — люди просто не понимают, что они используют версию библиотеки от 2008 года.
4. Использовать последние версии Apache HttpComponents
В плюсах имеем то, что мы продолжаем использовать всё тот же код, не тратя время на переписывание и на изучение новой библиотеки и её багов. Более того, код написанный на HttpComponents будет у вас работать где угодно. Библиотека чудовищно мощная и позволяет вам сделать действительно что угодно.
Вопрос — как её подключать?
Если просто брать и подставлять в gradle, то выйдет конфликт классов с этой самой древней версией.
В релизе 4.3 разработчики apache предлагали использовать специальные постфиксы “HC4” в классах для избегания конфликтов, но работало это как-то очень плохо.
Зато к релизу 4.5 они уже рекомендуют другой, единственно логичный выход — использовать перепакованную под другим пространством классов библиотеку, собранную неким товарищем на гитхабе (ссылка ниже). Там, правда, на самом деле версия 4.4, а не 4.5 — но это не так принципиально. А если вас волнует, что вы используете собранную непонятно кем библиотеку (хотя она довольно популярна), то вы всегда можете собрать её сами из исходников. На данный момент я считаю это наиболее правильным выходом из сложившейся дурной ситуации (и сам делаю именно так).
Что дальше?
Заметок по использованию пятой версии библиотеки на Android пока что нету — возможно, это объясняется тем, что она пока ещё в альфа версии. Или просто в Apache решили больше не иметь дела с Google и Android. Впрочем, даже если так — всегда найдутся энтузиасты, которые смогут корректно перепаковать последнюю версию. А работать с ней — сплошное удовольствие.
Ссылки
- Репозиторий с перепакованной версией httpComponents;
- Заметки о релизе 4.5, где рекомендуют использовать этот репозиторий;
- Интересные фрагменты переписки ребят из Apache и Google;
- Любопытные задачи с комментариями в джире Apache — раз и два.
В процессе написания статьи я общался на эту тему с разработчиком из Apache, который подтвердил мои предположения, но от греха подальше упоминать его здесь не буду.
Комментарии (40)
Akon32
25.08.2016 16:21+2Google довольно часто руководствуется принципом "здесь и сейчас", игнорируя правила, и через некоторое время пользователи остаются наедине с негибкими решениями.
Навскидку:- Описанная ситуация с нестабильной версией HttpComponents (включить нестабильную библиотеку в ОС, и тем самым запретить её обновления без костылей? серьёзно?)
- Ограничение 64К методов в .dex (конечно, этого хватит всем).
- Отсутствие generic-ов в go (сойдёт и так, пока вам не понадобятся самописные коллекции).
- Отказ от использования исключений в языках, в которых они есть (в их библиотеках на С++ и по инерции на Java).
- Нестандартное для Java соглашение об именовании полей (префикс m).
rimidalvv
25.08.2016 16:42По некоторым пунктам я думаю были реальные причины сделать так.
- Например dex был придуман когда телефоны были еще совсем маленькими. Еще кнопочными. И придумало его не гугл.
- Префикс m это Венгерская нотация. Очень удобно раньше было. Понятно что сейчас IDE тебе все подскажут. Так исторически сложилось. Было написанно много кода и сейчас что либо менять, не имеет смысла.
Но если честно, то дело то не в корпорации. Дело в конкретных людях и принимаемых ими решениях. Создается впечатление что иногда когда произносят Google/Apple/Microsoft то априори там сидят люди и думаю: «Тааак, сейчас я выпилю эту либу, а потом перейду в другую контору и напишу свою на основе выпиленной и буду молодец.»
rimidalvv
25.08.2016 16:29+3Я из статьи не до конца понял, чем вас не устроило OkHttp, кроме того что это «неприятно пахнущий форк»? Или вы говорите про какие то проекты которые существуют более 6 лет?
jehy
25.08.2016 16:32+2Меня не устроила библиотека OkHttp своей историй и тем, что в её использовании нет смысла, если есть хороший оригинал. Преимуществ OkHttp над Apache HttpComponents я не видел — возможно они есть, с интересом прочту.
Artem_zin
26.08.2016 03:43+1В целом, у OkHttp просто нормальный апи из коробки, Apache Http без всяких врапперов просто невозможно пользоваться (ощущения очень схожи с употреблением Calendar API вместо JodaTime/ThreeTen)
jehy
26.08.2016 11:50Гмм, у меня таких проблем не было, но для простоты есть FluentApi:
Request.Get("http://targethost/homepage") .execute().returnContent(); Request.Post("http://targethost/login") .bodyForm(Form.form().add("username", "vip").add("password", "secret").build()) .execute().returnContent();
Вроде легко и понятно.Artem_zin
26.08.2016 12:11+3Ну как бы, то что у библиотеки есть отдельная библиотека с Fluent API говорит о плохом дизайне основной библиотеки.
jehy
26.08.2016 12:28Да не так чтобы — это довольно стандартный подход, когда появляются упрощающие низкоуровневую работу вещи, примеры сплошь и рядом. Например, наличие ORM никоим образом не означает, что SQL плох.
Artem_zin
26.08.2016 12:47Всё таки посмотрите API OkHttp. Аналогия с SQL плохая, более корректно было бы сказать что есть HTTP (SQL) и ORM (OkHttp, FluentApacheHttp).
jehy
26.08.2016 14:31А можете сказать, есть ли там поддержка SSL прокси и возможность обращения к айпишнику с произвольным именем хоста? Это не надуманный пример, это те самые задачи, которые у меня возникали. Интересно просто. Вроде никакой чёрной магии…
Artem_zin
26.08.2016 15:00+3Прокси, конечно, поддерживаются. Более того, там есть Interceptors API который позволяет вклиниваться в механизм выполнения запросов и делать всё, что угодно.
Про обращение к айпишнику с произвольным именем хоста — если я правильно понял, то это решается заголовком
Host
и запросом на конкретный IP.
Prototik
25.08.2016 16:36использовать перепакованную под другим пространством классов библиотеку, собранную неким товарищем на гитхабе (ссылка ниже). Там, правда, на самом деле версия 4.4, а не 4.5 — но это не так принципиально
Всегда можно самому собрать подобную сборку с помощью jarjar. Да и доверия у ней будет больше, чем «собранной неким товарищем».jehy
25.08.2016 16:45На самом деле, всё не совсем так просто, я просто не стал упоминать все технические моменты. Текущие релизы от Apache не рассчитаны под Android, так что при своей пересборке нужно будет не только поменять пространство классов, но и как минимум:
Commons Logging replaced with Android Logging.
Base64 implementation from Commons Codec replaced with Android Base64.
Android default SSLSocketFactory used by for SSL/TLS connections.
Это из заметок к апачевскому релизу 4.3.
Хотя я как раз и написал, что лучше всего собрать самому.
TheKnight
25.08.2016 16:59Для name shadowing есть хороший плагин для gradle под названием shadow-jar. Возможно, стоит к нему присмотреться.
jehy
25.08.2016 17:01Да, крутая штука, я уже пользовался ей, когда в приложение нужно было включать библиотеку, которая уже есть в другой подключенной библиотеке. К сожалению, минус в том, что я написал выше — готовой сборки последних версий от Apache под Android просто нет.
Beholder
25.08.2016 16:59А загрузить свою версию библиотеки при помощи своего класслоадера нельзя?
jehy
25.08.2016 17:13Не знаю, не видел такого подхода. Работающий пример с библиотеками Apache было бы очень интересно посмотреть. Навскидку возникает вопрос в быстродействии.
Akon32
25.08.2016 18:13Класслоадер по-хорошему сначала должен искать стандартные классы (которые уже есть у его родителя), а потом — свои.
Revertis
25.08.2016 17:20+1Как разработчик под Андроид со стажем, скажу — ничего необычного. Гугл есть гугл. Хуже них только Самсунг, который берет худо бедно работающий Андроид и портит во всех местах перед установкой на устройства.
Valle
25.08.2016 18:19+3В Apache HTTP нет дефолтного кеширования, компрессии, поддержки HTTP 2, cert pinning, автоматического повтора запросов, нормального пула соединений, как любая апач библиотека она огромна и поэтому годится только для десктопов. Я что-то совсем не вижу причин почему кто-то на андроиде может выбрать сейчас апач как HTTP движок для своего приложения кроме как нежелания перейти на что-то более современное.
jehy
25.08.2016 18:32+1Ваша неправда.
- Автоматические повторы запросов есть.
- Пул соединений точно есть, причём с кучей настроек.
- Насчёт «огромна» — полная библиотека весит мегабайт. При этом в вашем приложении она ни разу не целиком будет.
- Работа над поддержкой HTTP 2 идёт в пятой версии, но скажем честно — сейчас она никому не сдалась.
- А зачем вы делаете из мобильного приложения повторные запросы, которые будут кешированы?
Ещё аргументы есть? Не спор ради спора, хочется правда понять.Valle
25.08.2016 18:39Ну ладно, в большинстве фигню написал ) Но смысл в том что на андроиде okhttp обновляется чуть ли не каждый месяц а апач — нет. Как следствие — okhttp выглядит и чувствуется как более современная удобная и быстрая библиотека.
jehy
25.08.2016 18:50+1Ну так стоковая — да, с 2008 не обновляется. А настоящая обновляется раз в месяц. Собственно речь в посте и была о том, что возможность нормального обновления была фактически сломана гуглом. Именно для создания видимости того, что их форк лучше.
SukharevAndrey
25.08.2016 18:38Если заглянуть в список лицензий открытого ПО, используемого такими приложениями, как Google и Play Market, то можно увидеть, что там используется OkHTTP.
http
25.08.2016 19:15+4О да, товарищ, вы я смотрю, разработчик с опытом…
Во-первых, начиная с Андроида 4.2, под фасадом HttpConnection как раз OkHttp и используется, если бы вы разрабатывали под Андроид, то увидели бы это в стек-трейсе (пруф: https://android.googlesource.com/platform/external/okhttp/)
Под компресией подразумевается использование gzip по-умолчанию.
А теперь по-поводу Google Compression Proxy — он в любом случае не работает при использовании https, а если вы не использует https, то какая тогда по сути разница — за вами может любой следить. И я даже не знаю как его использовать в аппах, это вроде как фича Chrome browser.jehy
25.08.2016 19:33-1Гмм, не мог предположить, что они так торжественно gzip приподнесли, который есть уже черт знает сколько времени у apache.
Про то, что под фасадом okhttp — да, не знал, но и обратного тоже не говорил, так что не вижу ничего некорректного в моих утверждениях.
По поводу Google compression proxy — я писал статью как его использовать.
jehy
26.08.2016 11:45-1Уточнение — да, посмотрел, OkHttp используется для HttpUrlConnection — правда, с версии 4.4, а не 4.2, как вы сказали. Что, впрочем, непринципиально и значения особого не имеет.
А если бы вы внимательно читали статью, то поняли бы, почему я не видел этого в стек трейсах — просто потому, что у HttpUrlConnection тупо не хватало для моих нужд и я использовал httpComponents.http
28.08.2016 20:57+1К сожалению, впечатление от вашей статьи типа «Злой брат Гугл говорит вам не пользоваться Apache HttpComponents, чтобы следить за вами, а пользоваться надо вот этой».
Просто HttpUrlConnection (читай OkHttp) хватает в 99% случаях, просто у вас был очень специфический случай.jehy
28.08.2016 21:05Возможно, именно так и сложилось — но я скорее хотел сказать, что не стоит отказываться от уже используемого httpComponents в погоне за не совсем адекватной рекомендацией Google.
Сложился пост именно из-за огромного количества вопросов на stack overflow и issues на github, где говорили, что надо вот срочно переезжать с httpComponents на okHttp.
kemsky
26.08.2016 04:45Сначала зачем-то ее включили (что само по себе ошибка), включили и бросили умирать, это за гранью. Если писать с 0, то конечно можно выбрать либу по вкусу, но иногда надо встроить готовое решение (это же джава), которое использует апач другой версии и тут начинается пляска с переименованием сорцов, что, мягко говоря, бесит.
artemgapchenko
26.08.2016 11:46+2А вы всё ещё помните милого парня Джесси Вилсона из Dalvik team?
Да.
А вы знаете, что сейчас он работает в Square? И именно он является создателем OkHttp?
Да.
Более того, вы знате, что OkHttp начинался как форк куска AOSP (Android Open Source Project), который в свою очередь брал свой код из Apache Harmony?
Да.
Так что это и есть по сути создание форка Apache с последующим выкидыванием оригинала из обращения (второй вариант из озвученных ранее Джесси в общении с Apache). Звучит довольно гнусно, не правда ли? Единственное что непонятно — была ли это инициатива Google или самого Джесси. Но поступил он крайне некрасиво, выкинув конкурентов с помощью Google и придя весь в белом со своим решением.
Я конечно не знаю всей истории, но в вашем изложении это звучит как какая-то теория заговора. Может быть всё куда проще, и OkHttp был создан как попытка улучшить HttpComponents? Может апачевская библиотека страдает от каких-то косяков, которые можно было исправить только переписыванием всего проекта, что Вилсон и сделал в OkHttp? Я не сравнивал их api, но может быть на OkHttp советуют переходить потому, что он лучше/удобнее/написан с учётом работы на Android-устройствах, в отличие от апачевского оригинала, а не потому, что Google и Вилсон коварны и вероломны?jehy
26.08.2016 11:55-2Выводов я специально старался не делать, поскольку и без них звучит как теория заговора. Но гораздо чаще всё объясняется глупостью или жадностью.
Насчёт предположения о косяках — советую почитать ссылки на лист рассылки и джиру. Там видно, что каких-то косяков нет, и Apache были готовы исправлять что бы то ни было. А Вилсон сначала говорил, что у него нет времени на коммуникации, и вообще они все заняты в других «горящих» частях проекта… А затем написал свою библиотеку. Ну правда — не вижу я там каких-то технических проблем.artemgapchenko
26.08.2016 11:59+2Послушайте вот этот выпуск подкаста Fragmented, там как раз Вилсон давал свой взгляд на проблемы HttpComponents, и причины, побудившие его создать OkHttp. Просто сейчас ваша статья излагает довольно односторонний взгляд на проблему.
jehy
26.08.2016 12:23-2К сожалению, подскасты и ютуб ролики это не мой формат — при попытке просмотра чувствую уходящее напрасно время. Буду страшно вам благодарен за выжимку или отдельную статью.
Предполагаю, что там речь шла о невозможности менять интегрированную в систему библиотеку без breaking changes. Но тут можно было легко поступить так же, как сделали с android.camera. Просто появился новый пакет android.camera2, старый можно было использовать, но с предупреждениями о том, что он не рекомендуется, deprecated и все дела. И тут ситуация была бы даже лучше. camera2 можно использовать только с API 21 (что ведёт к аду с имплиментацией обеих версий. А по факту все забивают и используют старый camera), в то время как подключаемую извне httpComponents можно было бы использовать в любых версиях API.
Взгляд у меня может быть односторонний, но основанный на комментариях самого Вилсона. Думаю, вы не сможете не согласиться с тем, что качество коммуникации было ужасно. И это ответы самого Вилсона, никто за них его не писал.
artemgapchenko
26.08.2016 11:55+2К сожалению, большинство разработчиков слепо верят Google и сразу считают, что библиотека Apache “плохая”, и нужно бежать выкидывать её из своего кода.
После этого ожидал увидеть сравнение OkHttp и Apache HttpComponents, чтобы понять, какая из бибилотек объективно лучше. Вместо этого увидел следующее:
Например, тот же OkHttp. Сам его не пробовал, но говорят, что библиотека хорошая… Ну и касательно именно OkHttp — я бы не стал использовать столь неприятно пахнущий форк.
Ну вот, точно такое же предубеждение и слепая вера в то, что HttpComponents лучше, потому что он пахнет приятнее.jehy
26.08.2016 12:07Вы вырвали кусок из текста. На самом деле, там я писал о том, что глупо тратить время на выкидывание и новую реализацию той вещи, которая и так отлично работает. Об этом собственно и статья, заголовок никаких сравнений не обещает.
Насчёт OkHttp — это не предубеждение, а личная позиция на основании проведённых исследований. Данная позиция может обуславливаться не только техническим характеристиками кода.
alex_ter
Похожая ситуация со встроенной в Mac OS OpenSSL.
jehy
Удивительно, что по описанной мной проблеме нет никаких статей и описаний. Копал материалы с нуля, руководствуясь только дичайшим удивлением «и как это так вышло?!»
Akon32
В своё время я пытался переупаковать HttpComponents с помощью maven shade и gradle на этапе сборки (не вышло, где-то использовалась рефлексия), а потом я увидел, что авторы HttpComponents (не от хорошей жизни) предоставляют отдельную сборку под android с другими названиями классов. Но этот вариант никак не подходил (я писал библиотеку, зависящую от HttpComponents), и пришлось отказаться от HttpComponents.
Это печально. Надо бы предусмотреть пункт в лицензии библиотек на случай подобного использования.
roller
brew install openssl