您的位置:首页 > 其它

Volley Manager 不能发送Header的Bug

2016-01-14 14:40 288 查看
项目中使用了Volley Manger这个库,是对Volley又封装了一层,实现了一个通用的ByteArrayRequest,可以用来发送各种类型的请求,也可以上传文件。

只有很少的9个类,却大大简化了Volley的使用。

但是在与服务器端保持Session的时候,出现了问题,Session一直保持不了。

查看运行日志,获取SessionID和发送SessionID时是都正确,就是多次登录服务器会返回多个不同的SessionID,于是怀疑sessionid根本就没有发送给服务器端。Volley是个通用的网络请求库,问题应该不大,于是怀疑,Volley Manager在使用Volley时可能不当,导致header没有发送出去。

抓包验证了上述结论,自己填充的Cookie的header确实没有发送出去。

之前读过Volley的一部分源码,问题定位到Volley Manager自定义的ByteArrayRequest#getHeaders()

其实现如下:

@Override
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String, String> headers = super.getHeaders();
if (null == headers || headers.equals(Collections.emptyMap())) {
headers = new HashMap<String, String>();
}
return headers;
}
查看super.getHeaders()的实现:

public Map<String, String> getHeaders() throws AuthFailureError {
<span style="white-space:pre">	</span>return Collections.emptyMap();
}
问题出现了,当Volley框架调用xxxRequest的getHeaders方法时,肯定是个空的HashMap。

于是修改ByteArrayRequest类的相关部分实现。

增加下面的域定义:

private Map<String,String> mHeaders = new HashMap<String, String>();
修改getHeaders方法如下:

public Map<String, String> getHeaders() throws AuthFailureError {
<span style="white-space:pre">	</span>return this.mHeaders;}

为什么修改XXXRequest的getHeaders方法可以?

原因如下:

Volley里面的RequestQueue是Volley的最最核心的类,RequestQueue可以将Volley所有的类串起来。

其内部保持了一个NetworkDispatcher的数组,NetworkDispatcher继承自Thread类,是一个线程,用于调度处理走网络的请求。启动后会不断从网络请求队列中取请求处理,队列为空则等待,请求处理结束则将结果传递给ResponseDelivery去执行后续处理,并判断结果是否要进行缓存。他跟加到RequestQueue中的Request对象一一对应。

NetworkDispatcher的run方法如下:

@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
while (true) {
long startTimeMs = SystemClock.elapsedRealtime();
Request<?> request;
try {
// Take a request from the queue.从队列中取出一个Request对象
request = mQueue.take();
} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.
if (mQuit) {
return;
}
continue;
}

try {
request.addMarker("network-queue-take");

// If the request was cancelled already, do not perform the
// network request.
if (request.isCanceled()) {
request.finish("network-discard-cancelled");
continue;
}

addTrafficStatsTag(request);

// Perform the network request.执行请求,最最核心的一行代码
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete");

// If the server returned 304 AND we delivered a response already,
// we're done -- don't deliver a second identical response.
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
request.finish("not-modified");
continue;
}

// Parse the response here on the worker thread.解析请求的响应
Response<?> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");

// Write to cache if applicable.
// TODO: Only update cache metadata instead of entire record for 304s.
if (request.shouldCache() && response.cacheEntry != null) {
mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}

// Post the response back.
request.markDelivered();
mDelivery.postResponse(request, response);
} catch (VolleyError volleyError) {
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
parseAndDeliverNetworkError(request, volleyError);
} catch (Exception e) {
VolleyLog.e(e, "Unhandled exception %s", e.toString());
VolleyError volleyError = new VolleyError(e);
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
mDelivery.postError(request, volleyError);
}
}
}


具体看上边的注解就可以了。最最核心的一行代码牵扯到一个Network对象。要看Network的作用,可以看一下Volley提供的Network的基本实现BasicNetwork类。

BasicNetwork#performRequest的实现如下:

@Override
public NetworkResponse performRequest(Request<?> request)
throws VolleyError {
long requestStart = SystemClock.elapsedRealtime();
while (true) {
HttpResponse httpResponse = null;
byte[] responseContents = null;
Map<String, String> responseHeaders = new HashMap<String, String>();
try {
// Gather headers.
Map<String, String> headers = new HashMap<String, String>();
addCacheHeaders(headers, request.getCacheEntry());

httpResponse = mHttpStack.performRequest(request, headers);
responseHeaders.putAll(httpResponse.getAllHeaders());
int statusCode = httpResponse.getResponseCode();
// Handle cache validation.
if (statusCode == HttpResponse.SC_NOT_MODIFIED) {
Entry entry = request.getCacheEntry();
if (entry == null) {
return new NetworkResponse(
HttpResponse.SC_NOT_MODIFIED, null,
responseHeaders, true,
SystemClock.elapsedRealtime() - requestStart);
}

// A HTTP 304 response does not have all header fields. We
// have to use the header fields from the cache entry plus
// the new ones from the response.
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5 entry.responseHeaders.putAll(responseHeaders);
return new NetworkResponse(HttpResponse.SC_NOT_MODIFIED,
entry.data, entry.responseHeaders, true,
SystemClock.elapsedRealtime() - requestStart);
}

// Handle moved resources
if (statusCode == HttpResponse.SC_MOVED_PERMANENTLY
|| statusCode == HttpResponse.SC_MOVED_TEMPORARILY) {
String newUrl = responseHeaders.get("Location");
request.setRedirectUrl(newUrl);
}

// Some responses such as 204s do not have content. We must
// check.
if (httpResponse.getEntity() != null) {
responseContents = entityToBytes(httpResponse.getEntity());
} else {
// Add 0 byte response as a way of honestly representing a
// no-content request.
responseContents = new byte[0];
}

// if the request is slow, log it.
long requestLifetime = SystemClock.elapsedRealtime()
- requestStart;
logSlowRequests(requestLifetime, request, responseContents,
httpResponse);

if (statusCode < 200 || statusCode > 299) {
throw new IOException();
}
return new NetworkResponse(statusCode, responseContents,
responseHeaders, false, SystemClock.elapsedRealtime()
- requestStart);
} catch (SocketTimeoutException e) {
attemptRetryOnException("socket", request, new TimeoutError());
} catch (MalformedURLException e) {
throw new RuntimeException("Bad URL " + request.getUrl(), e);
} catch (IOException e) {
int statusCode = 0;
NetworkResponse networkResponse = null;
if (httpResponse != null) {
statusCode = httpResponse.getResponseCode();
} else {
throw new NoConnectionError(e);
}
if (statusCode == HttpResponse.SC_MOVED_PERMANENTLY
|| statusCode == HttpResponse.SC_MOVED_TEMPORARILY) {
VolleyLog.e("Request at %s has been redirected to %s",
request.getOriginUrl(), request.getUrl());
} else {
VolleyLog.e("Unexpected response code %d for %s",
statusCode, request.getUrl());
}
if (responseContents != null) {
networkResponse = new NetworkResponse(statusCode,
responseContents, responseHeaders, false,
SystemClock.elapsedRealtime() - requestStart);
if (statusCode == HttpResponse.SC_UNAUTHORIZED
|| statusCode == HttpResponse.SC_FORBIDDEN) {
attemptRetryOnException("auth", request,
new AuthFailureError(networkResponse));
} else if (statusCode == HttpResponse.SC_MOVED_PERMANENTLY
|| statusCode == HttpResponse.SC_MOVED_TEMPORARILY) {
attemptRetryOnException("redirect", request,
new AuthFailureError(networkResponse));
} else {
// TODO: Only throw ServerError for 5xx status codes.
throw new ServerError(networkResponse);
}
} else {
throw new NetworkError(networkResponse);
}
}
}
}


可以看出,真正执行请求的不是BasicNetwork而是调用了HttpStack的performRequest方法。BasicNetwork只是在请求之前处理了一下请求头,请求响应之后,处理一下响应的结果。将请求结果转换为可以被ResponseDelivery处理的NetworkResponse对象。

下面看一下HttpStack,HttpStack是一个接口,Volley提供了一个java.net.HttpUrlConnection的实现HurlStack。

下面看一下HurlStack#performRequest方法的实现:

@Override
public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
throws IOException, AuthFailureError {
String url = request.getUrl();
HashMap<String, String> map = new HashMap<String, String>()
//重点在这里
map.putAll(request.getHeaders());
map.putAll(additionalHeaders);
if (mUrlRewriter != null) {
String rewritten = mUrlRewriter.rewriteUrl(url);
if (rewritten == null) {
throw new IOException("URL blocked by rewriter: " + url);
}
url = rewritten;
}
URL parsedUrl = new URL(url);
HttpURLConnection connection = openConnection(parsedUrl, request);
for (String headerName : map.keySet()) {
connection.addRequestProperty(headerName, map.get(headerName));
}
setConnectionParametersForRequest(connection, request);
// Initialize HttpResponse with data from the HttpURLConnection.
int responseCode = connection.getResponseCode();
if (responseCode == -1) {
// -1 is returned by getResponseCode() if the response code could not be retrieved.
// Signal to the caller that something was wrong with the connection.
throw new IOException("Could not retrieve response code from HttpUrlConnection.");
}
HttpResponse response = new HttpResponse(connection.getResponseCode(), connection.getResponseMessage());
response.setEntity(entityFromConnection(connection));
for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
if (header.getKey() != null) {
response.addHeader(header.getKey(), header.getValue().get(0));
}
}
return response;
}

可以看上边重点标记的部分:

map.putAll(request.getHeaders());
map.putAll(additionalHeaders);
第一行调用XXXRequest.getHeaders()获取Request对象的headers,第二行是获取在Network的实现里处理的请求头,比如BasicNetwork里是将Cache相关的头信息传递给了HttpStack#performRequest方法的第二个参数【主要是if-none-match,if-modified-since】。

源码解析到这里,原因就可以解释了。Volley框架是通过Request#getHeaders()方法获取用户放置在header的信息的,而Volley Manager自定义的ByteArrayRequest的getHeaders方法,每次都返回一个空的HashMap,所以开发者放置的头信息,Volley无法获取到。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: