一,服务器网络接口
Servlet接口访问的URL格式为:http://[服务器IP]:[服务器端口号(默认8080)]/[项目名称]/[Servlet名称]。其内部可以针对GET请求和POST请求作出不同的处理方式:doGet函数处理GET请求,doPost函数处理POST请求。所以只需要实现doPost函数。当然,为了方便通过浏览器测试网络接口(在地址栏输入URL,后面加上参数,“http:\\...?a=123&b=456”),可以在doGet函数中调用已经实现的doPost函数。
接下来主要分析注册接口,主要有三部分:获取验证码,校验验证码和注册基本信息。其中,获取和校验验证码的过程如下图(图1):
1.获取验证码
验证码通过天翼开放平台下发,需要在 天翼开放平台 进行申请,会赠送你20天,每天调用次数100次以下的套餐。自从6月1号收费后,又增加了一个限制,使用者需要配置10个一下的测试手机号,接口只对测试手机号开放。(感觉天翼越来越抠门了,之前是免费使用的,现在是收费,还限制使用。。。)
1.客户端提交手机号码到下发短信验证码接口,发起下发验证码请求。
2.服务器通过HTTPS协议,以app_id和app_secret为主要参数,访问天翼的令牌接口,获取access_token。
3.服务器通过HTTP协议,以app_id和access_token为主要参数,访问天翼的信任码接口,获取token。
4.服务器随机生成6位数字的验证码作为verify_code,并将verify_code和当前时间time(检查验证码是否超时,时效为2分钟)以用户ID(手机号)作为key,存储在session中。然后通过Http协议,以app_id,access_token,token,phone和verify_code为主要参数,访问天翼的下发验证码接口,实现验证码的下发操作。
二,Servlet使用HttpClient
/** 获得可用于访问Https的HttpClient */
private static CloseableHttpClient CreateSSLInsecureClient() {
try {
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null,
new TrustStrategy() {
// 信任所有
public boolean isTrusted(X509Certificate[] chain,String authType)
throws CertificateException {
return true;
}
}).build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);
return HttpClients.custom().setSSLSocketFactory(sslsf).build();
} catch (Exception e) {
logger.error("Https配置出现异常:", e);
}
return HttpClients.createDefault();
}
3.访问接口和得到返回数据(以令牌接口为例)
/**
* 调用令牌接口,获取access_token<BR/>
* <BR/>
*
* HTTPS请求格式:POST <BR/>
* 响应格式:JOSN
*
* @return access_token 如果调用接口失败,则返回null
*/
private static String GetAccessToken() {
String accessToken = null;
CloseableHttpClient httpClient = CreateSSLInsecureClient();
HttpPost httpPost = new HttpPost(Constants.GetVerifyCode.GET_ACCESS_TOKEN_URL);
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair(
Constants.GetVerifyCode.Params.GRANT_TYPE_KEY,
Constants.GetVerifyCode.Params.GRANT_TYPE_VALUE));
params.add(new BasicNameValuePair(Constants.GetVerifyCode.Params.APP_ID,
Constants.GetVerifyCode.APP_ID));
params.add(new BasicNameValuePair(
Constants.GetVerifyCode.Params.APP_SECRET,
Constants.GetVerifyCode.APP_SECRET));
try {
HttpEntity reqEntity = new UrlEncodedFormEntity(params);//自动编码特殊字符
logger.debug("获取access_token的请求参数:"
+ EntityUtils.toString(reqEntity));
httpPost.setEntity(reqEntity);
HttpResponse response = httpClient.execute(httpPost);
HttpEntity resEntity = response.getEntity();
if (resEntity != null) {
String resStr = EntityUtils.toString(resEntity);
JSONObject resJson = JSONObject.fromObject(resStr);
int resCode = resJson.getInt(Constants.GetVerifyCode.Params.RES_CODE);
if (resCode == 0)// 请求成功
{
accessToken = resJson.getString(Constants.GetVerifyCode.Params.ACCESS_TOKEN);
logger.debug("获取access_token成功 access_token=" + accessToken);
} else {
String resMsg = resJson.getString(Constants.GetVerifyCode.Params.RES_MSG);
logger.error("获取access_token失败 res_message=" + resMsg);
}
return accessToken;
}
} catch (Exception e) {
logger.error("调用令牌接口失败:", e);
} finally {
httpPost.releaseConnection();
try {
httpClient.close();
} catch (IOException e) {
logger.error("调用令牌接口时关闭HttpClient异常:", e);
}
}
return accessToken;
}
三,客户端网络连接
客户端网络连接的主要架构如下图。由于各个功能模块向服务器发起网络请求的步骤都是一样的,所以将其封装为一个工具类。但是,因为需要访问不同的服务器接口,返回相应的结果,以及处理的方式都不同,所以需要让每个功能模块的Activity实现回调函数接口,用于处理返回数据。
具体步骤如下:
1.Activity实现HttpCallBackListener接口,以及根据自身需求实现callBack函数,用于处理服务器返回的数据。
2.开启新的线程,整理参数,和通过HttpUtils的sendRequest方法发起网络请求。其中id为服务器的接口ID,data为RSA加密后的参数,this为当前实现了HttpCallBackListener接口的Activity。
3.HttpUtils的sendRequest方法会根据接口ID,设置对应URL。并且向服务器发起访问。
4.如果访问成功,则回调callBack函数。
public class ModifyPsdActivity extends Activity implements HttpCallBackListener{
private static final String TAG = ModifyPsdActivity.class.getSimpleName();
private Handler mMultiHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_menu_modify_psd);
mContext = this;
mMultiHandler = ThreadUtils.GetMultiHandler(TAG);
//发起修改密码的请求
mMultiHandler.post(new Runnable() {
@Override
public void run() {
JSONObject reqJson = new JSONObject();
try {
reqJson.put(Constants.UserInfo.RequestParams.USER_ID,CacheUtils.GetUserId());
reqJson.put(Constants.UserInfo.RequestParams.OLD_PSD,oldPsdMd5);
reqJson.put(Constants.UserInfo.RequestParams.PASSWORD,newPsdMd5);
Log.d(TAG, "修改登录密码请求,加密内容:" + reqJson.toString());
// RSA 加密
String data = EncryptUtils.GetRsaEncrypt(reqJson.toString());
HttpUtils.sendRequest(Constants.ID.MODIFY_PSD,data, ModifyPsdActivity.this);
} catch (ConnectTimeoutException e) {
Log.e(TAG, "网络连接超时", e);
// 提示用户
} catch (SocketTimeoutException e) {
Log.e(TAG, "系统繁忙", e);
// 提示用户
} catch (Exception e) {
e.printStackTrace();
}
}
});
/**网络连接成功后的回调函数*/
@Override
public void httpCallBack(int id, JSONObject resp) {
switch (id) {
case Constants.ID.MODIFY_PSD:
Log.d(TAG, "修改登录密码的回调函数");
String resCode = resp.optString(Constants.ResponseParams.RES_CODE);
if ("0".equals(resCode)) {
Log.d(TAG, "修改用户信息成功");
//提示用户
} else {
String resMsg = resp.optString(Constants.ResponseParams.RES_MSG);
Log.e(TAG, "修改登录密码失败 :" + resMsg);
//显示错误
}
break;
}
}
}
HttpUtils工具类
public class HttpUtils {
private static final String TAG = HttpUtils.class.getSimpleName();
/**
* 发送请求
*
* @param id
* 模块ID
* @param data
* RSA加密后的数据
* @param listener
* 响应的回调函数
* @throws ConnectTimeoutException
* @throws SocketTimeoutException
* @throws Exception
*/
public static void sendRequest(int id, String data,
HttpCallBackListener listener) throws ConnectTimeoutException,
SocketTimeoutException, Exception {
String url = null;
//需要上传photo的接口,则将photo作为一个参数进行传递
String photo = null;
boolean isNeedPhoto = false;
switch (id) {
case Constants.ID.GET_CODE:
url = Constants.Register.GET_VERIFY_CODE_URL;
break;
case Constants.ID.CHECK_CODE:
url = Constants.Register.CHECK_VERIFY_CODE_URL;
break;
case Constants.ID.REGISTER:
url = Constants.Register.REGISTER_URL;
break;
case Constants.ID.LOGIN:
url = Constants.Login.LOGIN_URL;
break;
case Constants.ID.MODIFY_USERINFO:
url = Constants.UserInfo.MODIFY_USERINFO_URL;
isNeedPhoto = true;
String[] strArr = data.split("&");
if(strArr.length == 2)//有photo的数据
{
photo = strArr[1];
}
data = strArr[0];
Log.d(TAG, "photo="+photo);
break;
case Constants.ID.MODIFY_PSD:
url = Constants.UserInfo.MODIFY_PSD_URL;
break;
case Constants.ID.GET_FRIEND_LIST:
url = Constants.GetFriendList.GET_FRIEND_LIST_URL;
break;
case Constants.ID.SEARCH_USER:
url = Constants.AddFriend.SEARCH_USER_URL;
break;
case Constants.ID.REMOVE_FRIEND:
url = Constants.RemoveFriend.REMOVE_FRIEND_URL;
break;
default:
Log.e(TAG, "业务ID不存在");
return;
}
Log.d(TAG, "url = " + url);
// HttpPost连接对象
HttpPost httpPost = new HttpPost(url);
//使用UTF-8编码
httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded; charset=utf-8");
// 使用NameValuePair来保存要传递的Post参数
List<NameValuePair> params = new ArrayList<NameValuePair>();
// 添加要传递的参数
params.add(new BasicNameValuePair(Constants.RequestParams.DATA, data));
if(isNeedPhoto)
{
params.add(new BasicNameValuePair(Constants.RequestParams.PHOTO, photo));
}
// 设置字符集
HttpEntity httpEntity = new UrlEncodedFormEntity(params, HTTP.UTF_8);
httpPost.setEntity(httpEntity);
// 使用自定义的HttpClient
HttpClient httpClient = CustomerHttpClient.getHttpClient();
// 取得HttpResponse
HttpResponse httpResponse = httpClient.execute(httpPost);
// HttpStatus.SC_OK表示连接成功
if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
Log.d(TAG, "连接成功");
// 取得返回的字符串
String respStr = EntityUtils.toString(httpResponse.getEntity());
JSONObject respJson = new JSONObject(respStr);
//回调
listener.httpCallBack(id, respJson);
} else {
Log.e(TAG, "连接失败");
}
}
}
自定义HttpClient,设置基本参数
public class CustomerHttpClient {
private static final String CHARSET = HTTP.UTF_8;
private static HttpClient customerHttpClient;
private CustomerHttpClient() {
}
public static synchronized HttpClient getHttpClient() {
if (customerHttpClient == null) {
HttpParams params = new BasicHttpParams();
// 设置一些基本参数
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(params, CHARSET);
HttpProtocolParams.setUseExpectContinue(params, true);
HttpProtocolParams.setUserAgent(params,
"Mozilla/5.0(Linux;U;Android 2.2.1;en-us;Nexus One Build.FRG83) "
+ "AppleWebKit/553.1(KHTML,like Gecko) Version/4.0 Mobile Safari/533.1");
// 超时设置
/* 从连接池中取连接的超时时间 */
ConnManagerParams.setTimeout(params, 2000);
/* 连接超时 */
HttpConnectionParams.setConnectionTimeout(params, 20000);
/* 请求超时 : 获取服务器响应超时*/
HttpConnectionParams.setSoTimeout(params, 20000);
// 设置我们的HttpClient支持HTTP和HTTPS两种模式
SchemeRegistry schReg = new SchemeRegistry();
schReg.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
schReg.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
// 使用线程安全的连接管理来创建HttpClient
ClientConnectionManager conMgr = new ThreadSafeClientConnManager(params, schReg);
customerHttpClient = new DefaultHttpClient(conMgr, params);
}
return customerHttpClient;
}
}