###0.前言
在App与后端接口的交互中,后端许多时候都需要app传递一些通用的参数作为请求的标识。通常会选择在HTTP报文的请求头中添加,个别会GET、POST的请求参数中添加。
举个例子:
后端需要通过请求报文判断当前请求的app版本、系统类别、系统版本、市场渠道等。那么就会要求app端将相关参数设置到请求头或者提交参数中,传递给后端。
APP端需要保证每一个接口都传递相关的参数,那么就有以下几种做法:
- 每一个接口都添加通用参数
- 封装通用的请求工具类,在工具类里面添加参数
- 配置拦截器对请求拦截配置
本文采用Retrofit + OkHttp的网络请求框架进行讲解
###1.常规的参数设置
@FormUrlEncoded
@POST(Api.User.LOGIN)
@Headers({
"Accept: application/json",
"User-Agent: NowyApp"
})
Observable<BaseEntity<LoginEntity>> login(@Field("phone") String mobile,
@Field("pwd") String psw);
@FormUrlEncoded
@POST(Api.User.GET_USER_INFO)
Observable<BaseEntity<LoginEntity>> getUserInfo(
@Header(ReqHeader.Key.HTTP_AUTHORIZATION)String token,
@Field("userId") String userId);
从上面代码实例可知,可以通过retrofit的@Headers注解或者@Header注解设置请求头,或者通过@Field、@Query设置提交参数。
所以,最适合初学者的方式就是每个接口配置通用参数。
需要注意的是:retrofit 2.0 以后提供了@HeaderMap注解。
###2.封装通用请求工具类
此处以post为例:
@FormUrlEncoded
Observable<BaseEntity<String>> post(@Url String url,
@HeaderMap Map<String,String> headerMap,
@FieldMap Map<String,String> postMap);
通过编写一个通用的提交方法,再编写一个HttpUtil类:
public static Observable<BaseEntity<String>> post(String url,Map<String,String> postMap){
Map<String,String> header = new HashMap<>(); //配置通用参数
header.put("Accept","application/json");
header.put("User-Agent","NowyApp");
return ApiManager.getRxNetWorkApi().post(url,header,postMap)
.onErrorResumeNext(new HttpResponseFunc<BaseEntity<String>>())
.subscribeOn(Schedulers.io());
}
通过上面的示例代码可知,我们可以通过封装将重复代码抽离,减少部分工作量。当然,缺点就是代码嵌套的深度更深了。
注意:由于retrofit使用的是动态代理的方式实现网络请求操作,所以接口声明不能使用泛型,否则会遭到类型擦除。
###3.使用OkHttp拦截器实现通用参数配置
在OkHttp中,提供了拦截器addInterceptor
和addNetworkInterceptor
让开发者对请求进行拦截和处理。其本质就是通过责任链的模式对OkHttp的request进行代理。具体源码可以查阅OkHttp的RealInterceptorChain
类。
所以,我们可以通过自定义拦截器Interceptor来封装通用参数。
此处为GitHub上提供的拦截器:
https://github.com/jkyeo/okhttp-basicparamsinterceptor
基本思路就是在拦截器中重新拼接请求参数,再整合为完整的请求。
不过,此方案的代码实现存在一个问题,那就是在拼接通用参数后没有动态更新报文长度,会造成后端接口参数不全问题。
下面给出修改后的完整的Basicparamsinterceptor
代码:
/**
* 设置全局通用的公共参数
* @link https://github.com/jkyeo/okhttp-basicparamsinterceptor
* post请求需要刷新Content-Length的数量
* MultipartBody拼装需要注意分割线(boundary)问题
*
*/
public class BasicParamsInterceptor implements Interceptor {
Map<String, String> queryParamsMap = new HashMap<>();
Map<String, String> paramsMap = new HashMap<>();
Map<String, String> headerParamsMap = new HashMap<>();
List<String> headerLinesList = new ArrayList<>();
private BasicParamsInterceptor() {
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Request.Builder requestBuilder = request.newBuilder();
// process header params inject
Headers.Builder headerBuilder = request.headers().newBuilder();
if (headerParamsMap.size() > 0) {
Iterator iterator = headerParamsMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
headerBuilder.add((String) entry.getKey(), (String) entry.getValue());
}
}
//按行添加请求头
if (headerLinesList.size() > 0) {
for (String line: headerLinesList) {
headerBuilder.add(line);
}
}
// process header params end
// process queryParams inject whatever it's GET or POST
if (queryParamsMap.size() > 0) {
request = injectParamsIntoUrl(request.url().newBuilder(), requestBuilder, queryParamsMap);
}
// process post body inject
if (paramsMap != null && paramsMap.size() > 0
&& request != null && request.method().equals("POST")) {
if (request.body() instanceof FormBody) {
FormBody.Builder newFormBodyBuilder = new FormBody.Builder();
if (paramsMap.size() > 0) {
Iterator iterator = paramsMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
newFormBodyBuilder.add((String) entry.getKey(), (String) entry.getValue());
}
}
FormBody oldFormBody = (FormBody) request.body();
int paramSize = oldFormBody == null ? 0 : oldFormBody.size();
if (paramSize > 0) {
for (int i=0;i<paramSize;i++) {
newFormBodyBuilder.add(oldFormBody.name(i), oldFormBody.value(i));
}
}
FormBody newFormBody = newFormBodyBuilder.build();
long newContentLength = newFormBody.contentLength();
requestBuilder.post(newFormBody);
updateHeader(headerBuilder,requestBuilder,newContentLength);
request = requestBuilder.build();
} else if (request.body() instanceof MultipartBody) {
MultipartBody multipartBody = ((MultipartBody)request.body());
List<MultipartBody.Part> oldParts = null ;
String boundary = null;
if(multipartBody != null){
boundary = multipartBody.boundary();
oldParts = multipartBody.parts();
}
/**
* 保证分割线(boundary)与请求头相同
*/
MultipartBody.Builder multipartBuilder;
if(boundary != null){
multipartBuilder = new MultipartBody.Builder(boundary).setType(MultipartBody.FORM);
}else{
multipartBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM);
}
Iterator iterator = paramsMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
multipartBuilder.addFormDataPart((String) entry.getKey(), (String) entry.getValue());
}
if (oldParts != null && oldParts.size() > 0) {
for (MultipartBody.Part part : oldParts) {
multipartBuilder.addPart(part);
}
}
MultipartBody newMultipartBody = multipartBuilder.build();
long newContentLength = newMultipartBody.contentLength();
requestBuilder.post(newMultipartBody);
updateHeader(headerBuilder,requestBuilder,newContentLength);
request = requestBuilder.build();
}
}else{
request = requestBuilder.build();
}
requestBuilder.headers(headerBuilder.build());
return chain.proceed(request);
}
private void updateHeader(Headers.Builder headerBuilder, Request.Builder requestBuilder, long newContentLength){
headerBuilder.set("Content-Length",String.valueOf(newContentLength));
requestBuilder.headers(headerBuilder.build());
}
private boolean canInjectIntoBody(Request request) {
if (request == null) {
return false;
}
if (!TextUtils.equals(request.method(), "POST")) {
return false;
}
RequestBody body = request.body();
if (body == null) {
return false;
}
MediaType mediaType = body.contentType();
if (mediaType == null) {
return false;
}
if (!TextUtils.equals(mediaType.subtype(), "x-www-form-urlencoded")) {
return false;
}
return true;
}
// func to inject params into url
private Request injectParamsIntoUrl(HttpUrl.Builder httpUrlBuilder, Request.Builder requestBuilder, Map<String, String> paramsMap) {
if (paramsMap.size() > 0) {
Iterator iterator = paramsMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
httpUrlBuilder.addQueryParameter((String) entry.getKey(), (String) entry.getValue());
}
requestBuilder.url(httpUrlBuilder.build());
return requestBuilder.build();
}
return null;
}
private static String bodyToString(final RequestBody request){
try {
final RequestBody copy = request;
final Buffer buffer = new Buffer();
if(copy != null)
copy.writeTo(buffer);
else
return "";
return buffer.readUtf8();
}
catch (final IOException e) {
return "did not work";
}
}
public static class Builder {
BasicParamsInterceptor interceptor;
public Builder() {
interceptor = new BasicParamsInterceptor();
}
public Builder addParam(String key, String value) {
interceptor.paramsMap.put(key, value);
return this;
}
public Builder addParamsMap(Map<String, String> paramsMap) {
interceptor.paramsMap.putAll(paramsMap);
return this;
}
public Builder addHeaderParam(String key, String value) {
interceptor.headerParamsMap.put(key, value);
return this;
}
public Builder addHeaderParamsMap(Map<String, String> headerParamsMap) {
interceptor.headerParamsMap.putAll(headerParamsMap);
return this;
}
public Builder addHeaderLine(String headerLine) {
int index = headerLine.indexOf(":");
if (index == -1) {
throw new IllegalArgumentException("Unexpected header: " + headerLine);
}
interceptor.headerLinesList.add(headerLine);
return this;
}
public Builder addHeaderLinesList(List<String> headerLinesList) {
for (String headerLine: headerLinesList) {
int index = headerLine.indexOf(":");
if (index == -1) {
throw new IllegalArgumentException("Unexpected header: " + headerLine);
}
interceptor.headerLinesList.add(headerLine);
}
return this;
}
public Builder addQueryParam(String key, String value) {
interceptor.queryParamsMap.put(key, value);
return this;
}
public Builder addQueryParamsMap(Map<String, String> queryParamsMap) {
interceptor.queryParamsMap.putAll(queryParamsMap);
return this;
}
public BasicParamsInterceptor build() {
return interceptor;
}
}
}
使用方式:
private static OkHttpClient.Builder buildOkHttp() {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.addInterceptor(createHttpLog());
builder.addNetworkInterceptor(
new BasicParamsInterceptor.Builder()
//-----------------------------header-----------------------
//添加手机型号
.addHeaderParam(ReqHeader.Key.HTTP_PHONETYPE, Build.MODEL)
.addHeaderParam(ReqHeader.Key.HTTP_PHONE_TYPE, "android")
//-----------------------------POST-----------------------
.addParam(ReqHeader.Key.HTTP_PHONETYPE, "android")
.build());
return builder;
}
注意点:如果通用参数是动态变化的,需要重新配置拦截器。
###4.总结
OkHttp通过拦截器让开发者更加灵活的扩展请求,实现个性化定制需求。前提是熟悉OkHttp项目。
END
–Nowy
–2019.02.11