您的位置:首页 > 理论基础 > 计算机网络

基于Volley和Gson的http网络请求设计(客户端和服务端)

2015-01-14 13:35 585 查看
经过对项目中http网络请求的多次修改,也算是一个很好的封装了,这个过程中查找了许多资料,发现网上零零散散的介绍比较多,并没有一个系统的例子讲解,这里给大家分享一下我的设计。

Volley和Gson都是Google为Android平台量身打造的框架,Volley是一个异步访问框架,Gson是对Json数据处理的框架,这里就不介绍了,假设大家已经知道怎么使用这两个框架了(这两个框架的用法都是很简单的)

客户端:Volley、Gson

服务端:SpringMVC

首先介绍数据封装对象ClientResponse<T>,每次我们请求服务端都会返回这么一个对象,它封装了返回的数据。

package com.runsdata.tower.android.message;

import java.io.Serializable;

/**
* 服务端返回的Json响应
*
* @author leo.lai
*
*/
public class ClientResponse<T> implements Serializable {

private static final long serialVersionUID = 1L;

/**
* 状态码
*/
private int statusCode;
/**
* 描述
*/
private String message;

/**
* 返回的数据
*/
private T data;

/**
* 附带值
*/
private Object accessory;

}
它由四个字段组成

statusCode:状态码——用来表示服务端返回的请求状态(成功、失败、超时、错误等)
message:描述——对响应的描述
data:返回的数据——返回的数据,它是一个泛型T类型
accessory:附带值——这个值不是必须的,需要传输多个值的时候才用上

下面是一个协议命令字定义类Protocol
public class Protocol {

// 请求成功状态码
public final static int TOWER_CODE_STATUS_SUCCESS = 0;
// 通用的或未知的错误
public final static int TOWER_CODE_STATUS_UNKNOWN_ERROR = 0x8000;
// session超时
public final static int TOWER_CODE_STATUS_SESSION_OVERTIME = 0x8001;
// 未找到(期望存在的)记录
public final static int TOWER_CODE_STATUS_DATA_NOT_FOUND = 0x8002;
// 无数据
public final static int TOWER_CODE_STATUS_NOTHING = 0x8003;
// 参数错误
public final static int TOWER_CODE_STATUS_PARAMETER_ERROR = 0x8004;
// 数据库操作时发生未知错误
public final static int TOWER_CMD_REQUEST_DATABASE_UNKNOWN_ERROR = 0x81001;
// 数据库插入数据时发生错误
public final static int TOWER_CMD_REQUEST_DATABASE_INSERT_ERROR = 0x8101;
}
这个类可以不用关注,由不同业务定义的不同状态常量,上面ClientResponse中的statusCode就是这里定义的。


--------------------------------------------------------------------客户端--------------------------------------------------------------------------

Volley框架给我们提供了很好的拓展,让我们可以根据自己的想法定义Request请求,这里定义一个GsonRequest
package com.runsdata.tower.android.volley.gson;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;

import android.util.Log;

import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import com.runsdata.tower.android.listener.HttpServiceListener;
import com.runsdata.tower.android.message.ClientResponse;
import com.runsdata.tower.android.message.Protocol;
import com.runsdata.tower.android.utils.SessionUtil;
import com.runsdata.tower.android.volley.AuthFailureError;
import com.runsdata.tower.android.volley.NetworkResponse;
import com.runsdata.tower.android.volley.Request;
import com.runsdata.tower.android.volley.Response;
import com.runsdata.tower.android.volley.Response.ErrorListener;
import com.runsdata.tower.android.volley.Response.Listener;
import com.runsdata.tower.android.volley.VolleyError;
import com.runsdata.tower.android.volley.toolbox.HttpHeaderParser;

/**
* 对ClientResponse请求进行了封装, 保持了sessionId,同时对请求错误做了统一处理
*/
public class GsonRequest<T> extends Request<ClientResponse<T>> {
public static final String TAG = "GsonRequest";

//响应监听器
private final Listener<ClientResponse<T>> mListener;

//请求参数Map
private final Map<String, String> params = new HashMap<String, String>();

//返回数据的类型
private Type mType;

/**
* 只处理成功的回调
* @param url
* @param type
* @param listener
*/
public GsonRequest(String url, Type type, Listener<ClientResponse<T>> listener) {
this(url, listener, null);
this.mType = type;
}

/**
* 对成功和失败的回调
* @param url
* @param type
* @param listener
*/
public GsonRequest(String url, Type type, CompletedListener<T> listener) {
this(url, listener, listener);
this.mType = type;
}

public GsonRequest(String url, Type type, HttpServiceListener<T> listener) {
this(url, listener, listener);
this.mType = type;
}

private GsonRequest(int method, String url, Listener<ClientResponse<T>> listener, ErrorListener errorListener) {
super(method, url, errorListener);
mListener = listener;
}

private GsonRequest(String url, Listener<ClientResponse<T>> listener, ErrorListener errorListener) {
this(Method.POST, url, listener, errorListener);
}

@Override
protected void deliverResponse(ClientResponse<T> response) {
mListener.onResponse(response);
}

/**
* 解析返回响应
*/
@Override
protected Response<ClientResponse<T>> parseNetworkResponse(NetworkResponse response) {
SessionUtil.parserSessionIdFromCookie(response.headers);

String parsed;
try {
parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
} catch (UnsupportedEncodingException e) {
parsed = new String(response.data);
}

ClientResponse<T> clientResponse = null;
try {
clientResponse = new Gson().fromJson(parsed, mType);

} catch (JsonSyntaxException e) {
e.printStackTrace();
return Response.error(new VolleyError("数据解析出错"));
}

/**
* 错误处理
*/
VolleyError error = processError(clientResponse);
if (error != null) {
return Response.error(error);
} else {
return Response.success(clientResponse, HttpHeaderParser.parseCacheHeaders(response));
}
}

/**
* 错误处理
*
* @param clientResponse
*/
private VolleyError processError(ClientResponse<T> clientResponse) {
String errorDesc = null;

if(clientResponse == null){
return new VolleyError("服务器无响应");
}

if (clientResponse.getStatusCode() == Protocol.TOWER_CODE_STATUS_SUCCESS) {
// 响应成功
return null;

} else {
int statusCode = clientResponse.getStatusCode();

switch (statusCode) {
case Protocol.TOWER_CODE_STATUS_UNKNOWN_ERROR:
errorDesc = "服务端异常";
break;
case Protocol.TOWER_CODE_STATUS_SESSION_OVERTIME:
errorDesc = "登录超时";
UITools.sendSessionOvertimeBroadcast();
break;
case Protocol.TOWER_CODE_STATUS_LOGIN_FAIL_NOT_FOUND:
errorDesc = "用户不存在";
break;
case Protocol.TOWER_CODE_STATUS_LOGIN_FAIL_DENY:
errorDesc = "用户状态异常不允许登录";
break;
case Protocol.TOWER_CODE_STATUS_LOGIN_FAIL_INVALID_PASSWORD:
errorDesc = "密码错误";
break;
default:
errorDesc = "访问网络发生错误";
break;
}
}

Log.i(TAG, errorDesc);
UITools.showTip(errorDesc);
return new VolleyError(errorDesc);
}

/**
* 设置请求的Header
*/
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String, String> headers = new HashMap<String, String>();
SessionUtil.addSessionCookie(headers);
return headers;
}

/**
* 得到请求参数
*/
@Override
protected Map<String, String> getParams() throws AuthFailureError {
return params;
}

/**
* 设置参数
*
* @param name
* @param value
*/
public void setParam(String name, String value) {
params.put(name, value);
}

/**
* 设置多个参数
* @param paramMap
*/
public void setParam(Map<String, String> paramMap) {
params.putAll(paramMap);
}

/**
* 去除某个参数
* @param key
*/
public void removeParam(String key){
params.remove(key);
}
}


现在对这个类中几个关键的部分进行解析,从上到下:

//响应监听器
private final Listener<ClientResponse<T>> mListener;

//请求参数Map
private final Map<String, String> params = new HashMap<String, String>();

//返回数据的类型
private Type mType;
mListener是响应成功的监听器,由构造方法传入或在构造方法中设置;

param是存放请求参数的Map;

mType是Gson中的类,用于解析Json数据时,指定解析类型

接着是几种构造方法的重载

/**
* 只处理成功的回调
* @param url
* @param type
* @param listener
*/
public GsonRequest(String url, Type type, Listener<ClientResponse<T>> listener) {
this(url, listener, null);
this.mType = type;
}

/**
* 对成功和失败的回调
* @param url
* @param type
* @param listener
*/
public GsonRequest(String url, Type type, CompletedListener<T> listener) {
this(url, listener, listener);
this.mType = type;
}

public GsonRequest(String url, Type type, HttpServiceListener<T> listener) {
this(url, listener, listener);
this.mType = type;
}

private GsonRequest(int method, String url, Listener<ClientResponse<T>> listener, ErrorListener errorListener) {
super(method, url, errorListener);
mListener = listener;
}

private GsonRequest(String url, Listener<ClientResponse<T>> listener, ErrorListener errorListener) {
this(Method.POST, url, listener, errorListener);
}
主要是用到前几个带Type参数的。

Http是无状态的协议,程序需要在Session保持状态,由于我们后台的使用Java实现的,保持session是根据解析请求头中的SessionId来判断身份,

所以我们每次请求都需要带上这个SessionId,至于SessionId如何获取的等到了相应代码再说明。

/**
* 设置请求的Header
*/
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String, String> headers = new HashMap<String, String>();
SessionUtil.addSessionCookie(headers);
return headers;
}


这里return的header将会设置成http请求的Header,在这里我们加上之前保存好SessionId的header,看下SessionUtil类

/**
* Session保持工具类
* @author leo.lai
*
*/
public class SessionUtil {

public static final String SET_COOKIE_KEY = "Set-Cookie";
public static final String COOKIE_KEY = "Cookie";
public static final String SESSION_COOKIE = "JSESSIONID";

/**
* 当前保持的sessionId
*/
public static String SESSIONID;

/**
* 从Cookie中得到SessionId
* @param headers
*/
public static void parserSessionIdFromCookie(Map<String, String> headers) {

if (headers.containsKey(SET_COOKIE_KEY) && headers.get(SET_COOKIE_KEY).startsWith(SESSION_COOKIE)) {
String cookie = headers.get(SET_COOKIE_KEY);
if (cookie.length() > 0) {
String[] splitCookie = cookie.split(";");
String[] splitSessionId = splitCookie[0].split("=");
SESSIONID = splitSessionId[1];
}
}
}

/**
* 给请求带上SessionId
* @param headers
*/
public static void addSessionCookie(Map<String, String> headers) {

if(SESSIONID == null)	return;

if (SESSIONID.length() > 0) {
StringBuilder builder = new StringBuilder();
builder.append(SESSION_COOKIE);
builder.append("=");
builder.append(SESSIONID);
if (headers.containsKey(COOKIE_KEY)) {
builder.append("; ");
builder.append(headers.get(COOKIE_KEY));
}
headers.put(COOKIE_KEY, builder.toString());
}
}

/**
* 得到设置好sessionId的header
* @return
*/
public static Header getSessionHeader(){
Header header = new BasicHeader(COOKIE_KEY, "JSESSIONID=" + SESSIONID);
return header;
}

/**
* 得到sessionId
* @return
*/
public static String getSessionId(){
return "JSESSIONID=" + SESSIONID;
}
}
这里主要是字符串的解析过程,组装成特定格式的Header

到了最关键的响应解析,当服务端返回响应后,会回调parseNetworkResponse(NetworkResponse response)方法,方法参数中的response中的data就是服务费返回的二进制格式数据

/**
* 解析返回响应
*/
@Override
protected Response<ClientResponse<T>> parseNetworkResponse(NetworkResponse response) {
SessionUtil.parserSessionIdFromCookie(response.headers);

String parsed;
try {
parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
} catch (UnsupportedEncodingException e) {
parsed = new String(response.data);
}

ClientResponse<T> clientResponse = null;
try {
clientResponse = new Gson().fromJson(parsed, mType);

} catch (JsonSyntaxException e) {
e.printStackTrace();
return Response.error(new VolleyError("数据解析出错"));
}

/**
* 错误处理
*/
VolleyError error = processError(clientResponse);
if (error != null) {
return Response.error(error);
} else {
return Response.success(clientResponse, HttpHeaderParser.parseCacheHeaders(response));
}
}


1.首先从response.headers中解析得到SessionId,这就是我们上面提到的;

2.将response.data生成Json字符串;

3.Gson的解析,clientResponse = new Gson().fromJson(parsed, mType);这里的mType是构造方法中传入的,会根据Type类型把json字符串转换成对应对象.

4.对错误情况进行处理;

5.return后会回调对应的成功或者失败的监听器。

/**
* 错误处理
*
* @param clientResponse
*/
private VolleyError processError(ClientResponse<T> clientResponse) {
String errorDesc = null;

if(clientResponse == null){
return new VolleyError("服务器无响应");
}

if (clientResponse.getStatusCode() == Protocol.TOWER_CODE_STATUS_SUCCESS) {
// 响应成功
return null;

} else {
int statusCode = clientResponse.getStatusCode();

switch (statusCode) {
case Protocol.TOWER_CODE_STATUS_UNKNOWN_ERROR:
errorDesc = "服务端异常";
break;
case Protocol.TOWER_CODE_STATUS_SESSION_OVERTIME:
errorDesc = "登录超时";
UITools.sendSessionOvertimeBroadcast();
break;
case Protocol.TOWER_CODE_STATUS_LOGIN_FAIL_NOT_FOUND:
errorDesc = "用户不存在";
break;
case Protocol.TOWER_CODE_STATUS_LOGIN_FAIL_DENY:
errorDesc = "用户状态异常不允许登录";
break;
case Protocol.TOWER_CODE_STATUS_LOGIN_FAIL_INVALID_PASSWORD:
errorDesc = "密码错误";
break;
default:
errorDesc = "访问网络发生错误";
break;
}
}

Log.i(TAG, errorDesc);
UITools.showTip(errorDesc);
return new VolleyError(errorDesc);
}
根据在Protocal中定义的状态对错误状态进行处理。

以上就是GsonRequest的整个定义,接下来我们看怎么使用它发起并处理请求,以登录为例子

GsonRequest<UserDto> loginRequest = new GsonRequest<UserDto>(Configuration.SERVER_ACCESS,
new TypeToken<ClientResponse<UserDto>>(){}.getType(),
new CompletedListener<UserDto>() {
@Override
public void onResponse(ClientResponse<UserDto> response) {
processAfterLogin(response);
}
@Override
public void onErrorResponse(VolleyError error) {
//                    Toast.makeText(mContext,error.getMessage(),Toast.LENGTH_SHORT).show();
}
}
);

loginRequest.setParam("username", username);
loginRequest.setParam("password", passwd);
loginRequest.setParam("cmd", Protocol.TOWER_CMD_REQUEST_USER_LOGIN + "");
executeRequest(loginRequest);


在创建GsonRequest的时候指定泛型对象UserDto,

new TypeToken<ClientResponse<UserDto>>(){}.getType(),指定响应的类型,这样在监听器Listener的onRequest方法中的返回值便是我们指定的这个类型,

参数中的“cmd”是请求的命令字,这是我们业务定义好的,服务端根据这个命令字区分不同的方法请求

由于自己的泛型设计能力不好,这里的GsonRequest<UserDto>和new TypeToken<ClientResponse<UserDto>>(){}.getType()有点重复的意思,暂时找不到更好的方法,先凑合着用,如果有更好的方法请告诉我。

以上就是客户端的完整设计

--------------------------------------------------------------------服务端--------------------------------------------------------------------------

服务端的Controller层是基于SpringMVC实现的,换成其他框架思想也是一样的。

服务端也需要开始的时候提到的两个ClientResponse<T>和Protocol

服务端的方法入口是唯一,根据请求的命令字分发request

首先配置拦截器

<!-- 拦截器 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/platform/tower/client/facade.ht" />
<mvc:mapping path="/platform/tower/client/test.ht" />
<bean class="com.hotent.mobile.interceptor.MobileAccessInterceptor">
<!-- 配置放行的CMD -->
<property name="excludeCMD">
<list>
<!-- 用户登录 -->
<value>0x0101</value>
<!-- 版本检测 -->
<value>0x0201</value>
</list>
</property>
</bean>
</mvc:interceptor>
</mvc:interceptors>
excludeCMD为放行的命令字(用户登录、版本检查),是不需要检测用户是否在线的。

接下来看看MobileAccessInterceptor.java

/**
* Session判断拦截器
*
* @author leo.lai
*
*/
public class MobileAccessInterceptor extends HandlerInterceptorAdapter {

private static final Logger logger = Logger
.getLogger(MobileAccessInterceptor.class);

private List<Short> excludeCMD;

@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {

String cmd = request.getParameter("cmd");
if (cmd == null) {
ClientResponse clientResponse = new ClientResponse();
clientResponse.setStatusCode(Protocol.TOWER_CODE_STATUS_UNKNOWN_ERROR);
clientResponse.setMessage("请求命令字为空");
writeJson(response,clientResponse);
}

// 1.判断命令字是否放行
if (excludeCMD.contains(Short.valueOf(cmd))) {
return true;
}

HttpSession session = request.getSession();
// 2.判断Session是否超时
SysUser sysUser = (SysUser) session.getAttribute("currentUser");
if (sysUser != null) {
// 设置后续用到的变量
ContextUtil.setCurrentUser(sysUser);
return true;
} else {

// 超时处理
logger.info("session超时,id = " + session.getId());
ClientResponse clientResponse = new ClientResponse();
clientResponse.setStatusCode(Protocol.TOWER_CODE_STATUS_SESSION_OVERTIME);
clientResponse.setMessage("Session超时");
writeJson(response,clientResponse);
return false;
}

}

/**
* 返回json
* @param response
* @param clientResponse
*/
public void writeJson(HttpServletResponse response,ClientResponse clientResponse) {
response.setContentType("text/json");
response.setCharacterEncoding("UTF-8");
try {
response.getWriter().write(new Gson().toJson(clientResponse));
} catch (IOException e) {
e.printStackTrace();
}
}

public List<Short> getExcludeCMD() {
return excludeCMD;
}

public void setExcludeCMD(List<Short> excludeCMD) {
this.excludeCMD = excludeCMD;
}
}
先判断命令字是否放行,接着从session中获得user对象判断是否为空(当然可以根据其他超时条件判断),如果超时则直接返回超时的ClientResponse对象;

接下来是程序的入口controller

/**
* 手机客户端访问入口
* @author leo.lai
*
*/
@Controller
@RequestMapping("/platform/tower/client")
public class ClientFacadeController{

private static final Logger logger = Logger.getLogger(ClientFacadeController.class);

@Resource
private ClientUserService userService;

/**
* 请求命令字
* @param request
* @param session
* @param cmd	请求命令字
* @return
*/
@RequestMapping(value = "facade",produces="application/json;charset=UTF-8")
@ResponseBody
public String facade(HttpServletRequest request,HttpSession session,int cmd){
String result = null;

try{
result = handleReuqest(request, session, cmd);
}catch(Exception e){
e.printStackTrace();
ClientResponse response = new ClientResponse();
response.setStatusCode(Protocol.TOWER_CODE_STATUS_UNKNOWN_ERROR);
response.setMessage(e.getMessage());
result = new Gson().toJson(response);
}

logger.info("[========Response content========] " + result);
return result;
}

/**
* 请求处理
* @param request
* @param session
* @param cmd
* @return
* @throws Exception
*/
public String handleReuqest(HttpServletRequest request,HttpSession session,int cmd) throws Exception{
String result = null;

switch (cmd) {
case Protocol.TOWER_CMD_REQUEST_USER_LOGIN:
result = userService.login(request, session);
break;
case Protocol.TOWER_CMD_REQUEST_SETTING_LATEST_VERSION:
result = versionControlService.getLatestVersion(request);
break;
<span style="white-space:pre">		</span>//等等。。。。。
case Protocol.TOWER_CMD_REQUEST_GET_PROCESSES:
result = myWorkService.getMyProcessCopyList(request);
break;
default:
break;
}
return result;
}


这里在handleRequest方法包含try_catch代码块,当catch到exception后则返回错误信息的ClientResponse,handlerRequest根据CMD命令字对请求进行分发,在这里根据业务调用对应的Service处理方法,

每个service方法返回的时候都必须构造一个ClientResponse对象,并用Gson( ).toJson方法序列化成json 字符串,最终返回到客户端处理。

以上就是整个Http请求的流程,有什么需要改正的地方敬请提出。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐