Android Volley BasicNetwork:performRequest:Unexpected response code 302 for http://xxx异常,这个http://xxx的接口在浏览器中打开可以正常访问。
接口做了302重定向,从http重定向到https的地址去了。
Volley对重定向并没有额外处理,我们可以看https://github.com/mcxiaoke/android-volley的代码对重定向的处理,处理详情见BasicNetwork类。
在该类里,当程序发现响应返回的状态码是301或302时,则改动原请求的请求URL为重定向URL,然后抛出异常。
if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
String newUrl = responseHeaders.get("Location");
request.setRedirectUrl(newUrl);//原请求指定重定向的URL
}
由于Volley有重试机制,对于响应失败的请求,会根据重试策略再次进行请求,此时,在while(true)里重新执行上次返回302的请求,则完成了对重定向的处理。
异常捕获的时候,对重定向状态单独处理,代码如下:
else if(statusCode==HttpStatus.SC_MOVED_PERMANENTLY||
statusCode==HttpStatus.SC_MOVED_TEMPORARILY) {
attemptRetryOnException("redirect",//attemptRetryOnException方法见前述
request,newRedirectError(networkResponse));//返回302则抛出重定向异常
}
但是这种对重定向的解决方案并不能满足一些场景,比如:
1、原请求是POST请求,但是重定向一般是GET请求,这样只改变原请求的访问地址,并不一定能请求成功。
2、重定向请求需要带的头信息与原请求不一致的,比如重定向是跨域的,可能需要对cookie处理。
除此之外,这种解决方案需要指定重试策略,但Volley默认是不重试的,而且我认为,大多数场景下,系统并不需要自动重试,而是由用户自己重新发起请求。这样一来,为了重定向需要单独指定重试策略,对代码的统一处理略有不便。
既然这种方案不能满足需求,那就需要改动源码了。
重定向的处理应该在访问请求的最底层处理比较合适,可以在HurlStack里处理。
自己处理重定向需要对connection加句代码:
connection.setInstanceFollowRedirects(false)
处理重定向的时候需要满足递归性,也就是说,A地址重定向到B地址,B地址又重定向到C地址,无限重定向,要能持续处理,并且原路返回结果。
HurlStack处理请求的方法是
public HttpResponse performRequest(Request request,Map additionalHeaders)
这里的入参是request对象,改动原request对象重新请求是比较麻烦的,而且由于是子线程里运行的,容易出现很多难解的问题。
理想的方式是构造新的请求,但是不能是构造新的Request对象,因为我们还要通过入参的request对象原路返回结果。
这时需要将performRequest方法里执行请求的代码提炼出一个方法来
private HttpResponse executeRequest(int method,String url,Map headers, byte[] postBody,
int timeoutMs,String contentType)
并且对重定向的处理如下:
if(responseStatus.getStatusCode() == HttpStatus.SC_MOVED_PERMANENTLY||
responseStatus.getStatusCode() == HttpStatus.SC_MOVED_TEMPORARILY) {
String redirectUrl = connection.getHeaderField("Location");
if(redirectUrl !=null&& redirectUrl.length() >0) {
return executeRequest(Method.GET,redirectUrl,headers, null,timeoutMs,contentType);
}
}
相应的,本类的一些其他的被调用方法由于入参类型问题也需要相应的变动,不过整体来说,改动还是很简单的。
另外可以增加两个方法onPrepareRequest、onReponseFinished;onPrepareRequest方法在请求调用之前执行,onReponseFinished可以在请求调用之后,重定向之前执行。空实现即可,需要的时候,在子类里处理即可,可以用于打印日志或者处理头信息等。
虽然改动量相对来说,略多,但是对一些场景来说,还是需要的。当然,如果重试机制的处理方式即可满足需求,那就无需动源码了。