Наши серверные коллеги тоже люди, так что API у них получаются разные. Кто-то передаёт параметры POST-ом, у кого-то работает gzip-компрессия, кто-то возвращает ошибки статусами, а у кого-то для них отдельный протокол. Попробую собрать наиболее частые вопросы и предложить решения.
Сделаю так. Приведу код класса ExtendedRequest<T>
— наследника Request<T>
, в котором решены все назревающие вопросы, и объясню, где именно и как они решаются. Соответственно, дальнейшие реквесты будет иметь смысл наследовать именно от него.
ExtendedRequest.java
public abstract class ExtendedRequest<T> extends Request<T> { private static final String HEADER_ENCODING = "Content-Encoding"; private static final String HEADER_USER_AGENT = "User-Agent"; private static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding"; private static final String ENCODING_GZIP = "gzip"; protected Map<String, String> mParams; private String mUserAgent; private boolean mGzipEnabled = true; protected final Response.Listener<T> mSuccessListener; public ExtendedRequest(int method, String url, Response.Listener<T> successListener, Response.ErrorListener errorListener) { super(method, url, errorListener); mSuccessListener = successListener; } @Override protected void deliverResponse(T response) { if (mSuccessListener != null) { mSuccessListener.onResponse(response); } } protected String getResponseString(NetworkResponse response) throws UnsupportedEncodingException { String responseString = null; String charset = HttpHeaderParser.parseCharset(response.headers); if (mGzipEnabled && isGzipped(response)) { try { byte[] data = decompressResponse(response.data); responseString = new String(data, charset); } catch (IOException e) { // it seems that result is not GZIP } } if (responseString == null) { responseString = new String(response.data, charset); } return responseString; } private boolean isGzipped(NetworkResponse response) { Map<String, String> headers = response.headers; return headers != null && !headers.isEmpty() && headers.containsKey(HEADER_ENCODING) && headers.get(HEADER_ENCODING).equalsIgnoreCase(ENCODING_GZIP); } protected byte[] decompressResponse(byte [] compressed) throws IOException { ByteArrayOutputStream baos = null; try { int size; ByteArrayInputStream memstream = new ByteArrayInputStream(compressed); GZIPInputStream gzip = new GZIPInputStream(memstream); final int buffSize = 8192; byte[] tempBuffer = new byte[buffSize]; baos = new ByteArrayOutputStream(); while ((size = gzip.read(tempBuffer, 0, buffSize)) != -1) { baos.write(tempBuffer, 0, size); } return baos.toByteArray(); } finally { if (baos != null) { baos.close(); } } } /** * Sets parameters map * @param params Parameters map */ public void setParams(Map<String, String> params) { mParams = params; } /** * Adds POST parameter * @param key Parameter name * @param value Parameter value */ public void addParam(String key, Object value) { if (mParams == null) { mParams = new HashMap<String, String>(); } mParams.put(key, String.valueOf(value)); } @Override protected Map<String, String> getParams() throws AuthFailureError { return mParams; } @Override public Map<String, String> getHeaders() throws AuthFailureError { Map<String, String> headers = new HashMap<String, String>(); // add user agent header if (TextUtils.isEmpty(mUserAgent)) { headers.put(HEADER_USER_AGENT, mUserAgent); } // add gzip header if (mGzipEnabled) { headers.put(HEADER_ACCEPT_ENCODING, ENCODING_GZIP); } return headers; } /** * Sets user agent to specify in request header * @param userAgent User agent string */ public void setUserAgent(String userAgent) { mUserAgent = userAgent; } /** Disables GZIP compressing (enabled by default) */ public void disableGzip() { mGzipEnabled = false; } /** * Sets request timeout * @param timeoutSec Timeout in seconds */ public void setTimeout(int timeoutSec) { setRetryPolicy(new DefaultRetryPolicy(timeoutSec * 1000, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT)); } }
Итак, поехали
Как передавать параметры POST-запроса?
Действительно, как? Никаких setParameters
у класса Request
нет, да и в конструкторе тишина.
А оказывается, что для передачи параметров нужно переопределить у реквеста функцию getParams
и вернуть мапу с нужными параметрами. Вроде:
public class SearchRequest extends Request<SomeResult> { … public Map<String, String> getParams() { Map<String, String> params = new HashMap<String, String>(); params.put("q", searchString); return params; } }
Однако, видов запросов может быть много и писать такое для каждого может быть лениво. Поэтому в ExtendedRequest
заведена специальная мапа и следующие методы:
addParam
— чтобы добавлять параметры по одному.setParams
— чтобы инициализировать список параметров всей кучей.
Для разных ситуаций может пригодиться либо один, либо второй.
Как задать timeout запроса?
Таймауты по умолчанию в Volley маленькие — 2500 мс. Так что если сервер нетороплив, достаточно часто будет возникать Request Timeout
.
В общем случае таймаут задаётся так:
Request<SomeClass> request = …; request.setRetryPolicy(new DefaultRetryPolicy(<+>TIMEOUT_MS, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
По-моему несколько нетривиальный пассаж, запомнить его вряд ли получится. Так что место ему в ExtendedRequest
под именем setTimeout
. А ещё лично мне больше нравится указывать таймаут в секундах.
Как добавить в запрос User-Agent?
User-Agent, как известно, передаётся в заголовках HTTP-запроса, так что вопрос наш переформулируется:
Как добавить в запрос заголовки?
Тут логика та же, что и с параметрами: переопределяем getHeaders
и возвращаем мапу с нужными заголовками. Так что можно по аналогии добавить локальное поле типа Map
, объявить публичный метод addHeader
и использовать его.
Но мне больше нравится другой подход. Так как заголовки у запросов в основном одни и те же (в основном, это только User-Agent
и Referer
), имеет смысл делать для каждого отдельный сеттер. Так что в ExtendedRequest
нет никакого addHeader
, но зато есть setUserAgent
и переопределён метод getHeaders
, в котором формируется мапа с заголовками, включая и юзер-агента. При необходимости можно добавить аналогичный сеттер для любого другого заголовка.
Как обработать GZIP-сжатие?
GZIP-сжатие – это хорошо, полезно и экономит пользователю трафик. Но создаёт программисту чуть-чуть дополнительной работы.
Для начала нужно попросить у сервера именно сжатый контент. Одни присылают gzip всегда, другие не умеют этого вовсе, третьи хотят соответствующий заголовок HTTP-запроса, а самые креативные завязываются на какой-нибудь GET- или POST-параметр.
Впрочем, соответствующий заголовок стоит добавлять всегда. Сервер, не поддерживающий gzip, просто не обратит на него внимания. В ExtendedRequest
реализация очень простая: есть флаг mGzipEnabled
, и, если он включён, в запрос добавляется заголовок Accept-Encoding=gzip
. По умолчанию данный флаг имеет значение true
, а выключить его можно с помощью метода disableGzip
.
Это что касается отправки запроса. И это только полдела: надо ещё получить ответ, определить, действительно ли он gzip, при необходимости распаковать, и только после этого парсить. Вся эта логика реализована в методе getResponseString
и сопутствующих приватных функциях. Метод объявлен, как protected
. Использовать его нужно в parseNetworkResponse
: строку ответа из экземпляра NetworkResponse
стоит получать именно таким образом.
Итого
Получился вполне себе неплохой базовый класс, от которого можно наследоваться и успешно грабить корованы.
Помимо все перечисленных фичей в ExtendedRequest
ещё реализована логика, связанная с success callback
, про который шла речь в предыдущей статье. Ибо в большей части случаев результаты запроса возвращаются именно таким образом.
Ещё есть, над чем работать. К примеру, я пока не очень понимаю, как правильно сделать кэширование. В каком-то виде в Volley оно есть, но нет у меня к нему доверия.
Комментариев нет:
Отправить комментарий
Пожалуйста, пишите содержательные комментарии и соблюдайте вежливость.