今天是圣诞节,虽说我本人对这个西方节日没什么感觉,但毕竟还是有很多小年轻人(自认为已然脱离年轻人的航道)挺在意这个节日的,在这里祝大家圣诞快乐吧(要是凑巧你也没什么感觉,那就预祝元旦快乐)!
闲话少叙,继续我们的正题。得益于上一篇文章【一步一个脚印】Tomcat+MySQL为自己的APP打造服务器(3-1)Android 和 Service 的交互之GET方式 所做的思路修改,我们完成了以HTTP GET 方式完成 Android 和服务器的交互(之后有朋友@可口可乐百事可乐 留言提了一些非常好的建议,比如说采用 json、HTML 格式的报文,优化代码等等,顺带提一下,我们本篇先把 POST 说完,然后再来处理这些内容),本篇我们就直接对比 GET方式来完成 POST 方式的交互。
上一篇我们说到 Android 发送 HTTP 请求通常使用2种方式:HttpURLConnection 和 HttpClient。下面我们还是分别使用这2种方式来实验完成 POST 方法的请求。
和 HttpURLConnection 发送 GET 请求的步骤类似,还是上次的代码,修改网络请求部分如下:
HttpURLConnection connection = null;
// 第一步:使用URL打开一个HttpURLConnection连接
URL url = new URL("http://192.168.1.117:8080/FirstServletService/PostTest"); // 声明一个URL,注意如果用百度首页实验,请使用https开头,否则获取不到返回报文
connection = (HttpURLConnection) url.openConnection(); // 打开该URL连接
// 第二步:设置HttpURLConnection连接相关属性
connection.setRequestMethod("POST"); // 设置请求方法,“POST或GET”,我们这里用GET,在说到POST的时候再用POST
connection.setConnectTimeout(80000); // 设置连接建立的超时时间
connection.setReadTimeout(80000); // 设置网络报文收发超时时间
// 如果是POST方法,需要在第3步获取输入流之前向连接写入POST参数
DataOutputStream out = new DataOutputStream(connection.getOutputStream()); // 在此之前也可以用connection.getResponseCode()获取返回码,看是否为200
String param = "account=wang&password=jie"; // 这里我们先模拟一个参数列表
out.writeBytes(param);
// 第三步:打开连接输入流读取返回报文,*注意*在此步骤才真正开始网络请求
InputStream in = connection.getInputStream(); // 通过连接的输入流获取下发报文,然后就是Java的流处理
// ...省略返回报文处理代码
唯一的不同就是发送 POST 请求时需设置 HttpURLConnection 的请求方式为 “POST”,然后写入 POST 的相应请求参数。这里我们要保证写入参数的步骤要在 connection.getInputStream() 之前就可以,因为这一句才真正发起网络请求,必须在发起网络请求之前完成相关设置。
如果到这里你就去运行,肯定是没结果的,因为我们的服务端代码还是只响应 GET 方法的代码,下面我们来做服务端 Servlet 的处理。新建一个 Servlet,这次我们不处理 doGet() 方法,只处理 doPost() 方法(参数获取方法一):@WebServlet("/PostTest")
public class PostTest extends HttpServlet {
// ...省略构造器和别的代码
/**
* 重写POST响应
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String account;
String password;
// 获取POST请求参数
// 肯定有人会发现,这不是和GET方法中获取参数的方法一样嘛!说实话,看起来是一样的,**这个问题先留下,我们后边还会提到**
account = request.getParameter("account");
password = request.getParameter("password");
LogUtil.log("account: " + account);
LogUtil.log("password: " + password);
// 响应
response.getWriter().append("你提交的账号为: ").append(account).append("\n密码:").append(password);
}
}
运行时候还是要注意,手机要能连上你的 Tomcat,然后 Run As > Run on Server成功后,再在手机端发起请求:
在 Servlet 的 POST 方法中,获取请求参数时我们还可以通过以下这种方式(参数获取方法二):
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String account;
String password;
BufferedReader reader = request.getReader();
String requestStr = reader.readLine();
LogUtil.log(requestStr); // 查看一下拿到的参数是个什么样的结构,然后有针对性的解析
HashMap requestMap = parseStrToMap(requestStr);
account =requestMap.get("account");
password = requestMap.get("password");
response.getWriter().append("你提交的账号为: ").append(account).append("\n密码:").append(password);
}
/**
* 针对POST方法中拿到的参数进行解析
*/
private HashMap parseStrToMap(String str) {
HashMap resultMap = new HashMap<>();
String[] items = str.split("&");
String[] itemStrs = new String[2];
for (String item : items) {
itemStrs = item.split("=");
resultMap.put(itemStrs[0], itemStrs[1]);
}
return resultMap;
}
把服务端程序保存重新运行,然后在 Android 端重新请求:
服务端的日志:
可以看到,我们 private HashMap
这里就有个问题,既然参数获取方法一能够如此简单就能获取参数,为什么还要用参数获取方法二多此一举自己来解析呢?我们返回来看看 HttpURLConnection 发送 POST 请求,我们这里添加了一个和 GET 方法 URL 后边拼接的一样的参数列表(问号?之后的部分),那又为啥就要这样拼接呢,out.writeBytes(String str) 接收一个String 类型的参数,这个字符串可不可以按别的方式拼接呢?这个问题我们要先看完 HttpClient 发送 POST 请求再回答。
和 HttpClient 发送 GET 请求步骤几乎一致,只是将 HttpGet 改成了 HttpPost,还是直接看代码:
new Thread(new Runnable() {
@Override
public void run() {
HttpClient client = new DefaultHttpClient(); // HttpClient 是一个接口,无法实例化,所以我们通常会创建一个DefaultHttpClient实例
HttpPost post = new HttpPost("http://192.168.1.117:8080/FirstServletService/PostTest"); // 改用POST请求,使用HttpPost
List params = new ArrayList<>();
params.add(new BasicNameValuePair("account", "wang"));
params.add(new BasicNameValuePair("password", "jie"));
try {
UrlEncodedFormEntity reqEntity = new UrlEncodedFormEntity(params, "utf-8");
post.setEntity(reqEntity); // 在client.execute() 方法前设置好请求体
HttpResponse httpResponse = client.execute(post); // 调用HttpClient对象的execute()方法
// 状态码200说明响应成功
if (httpResponse.getStatusLine().getStatusCode() == 200) {
HttpEntity entity = httpResponse.getEntity(); // 取出报文的具体内容
String response = EntityUtils.toString(entity, "utf-8"); // 报文编码
// 发送消息
Message msg = new Message();
msg.what = 1;
msg.obj = response;
handler.sendMessage(msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
运行一下,Servlet 中 doPost() 方法还是参数获取方法二来获取 HttpClient 发送过去的 POST 请求参数,看服务端日志:
看看服务端的日志打印,还是和我们用 HttpURLConnection 发送时得到的请求参数列表字符串是一样的,这次我们可没有手动去拼接任何形式的参数,使用的都是系统的API。说明了什么呢?说明系统 API 使用的就是这样的拼接方式,我们在 HttpURLConnection 中设置的参数拼接方式是照猫画虎手动拼接上去的。这就解释了上边问题的的一部分:即为什么这样拼接,为什么用系统的 request.getParameter() 方法能获取到参数值,因为我们用这种拼接方式“欺骗”了系统API,让它以为就是自己的东西!
接下来另一半问题:能不能用别的拼接方式来拼接这个参数列表呢?答案当然是——可以的!就像小时候做的物理题,小红在小明前方500米以4m/s的速度匀速前进,小明此时开始以 2m/(s*s) 的加速度开跑,问最终小明能追上小红吗(追上就让你嘿嘿嘿)?妈的,跑死都要追上!何况不用跑死就能追上,因为过不了3秒,我就比你跑的快了,必然能追上。我们这里为什么答案肯定是可以的呢?因为我们说过,今后可以用JSON、HTML格式标准化报文传输,所以其他拼接方式肯定是可以的,不然这个想法也不可能实现了,对吧?但是采用别的拼接方式后, request.getParameter() 方法就获取不到参数了,只有通过针对性的编写类似于 parseStrToMap(String str) 的解析方法才能拿到我们想要的参数,进而进行相应业务数据的处理。提示一下,HttpURLConnection 的参数输出流接收 String 类型的入参,你只要拼接的结果是字符串就可以了;但是 HttpClient 的HttpPost.setEntity() 方法接收一个 HttpEntity 作为入参,而 HttpEntity 是一个接口:
由这个实现或继承关系可以看出,StringEntity 也是可以用来作为参数拼接的载体传入 HttpPost 中。这里就不举例了,因为这注些一百度一大堆,也不是我们本篇的主要内容,等到 json格式报文传递再说。
如果 HttpClient 发送 POST 请求的例子你动手实践过,你会发现结果是这样的:
发现问题木有?两种方式显示的效果是不一样的,使用 HttpURLConnection 时没换行,而使用 HttpClient 就换行了,但是我们在 doPost() 方法中都是同样的带换行符的返回报文 response.getWriter().append("你提交的账号为: ").append(account).append("\n密码:").append(password); ,为何会出现这样的问题呢?呵呵,留待解决吧,我一开始以为是编码格式的问题导致换行符未识别,但是设置各种编码格式后发现还是没有改善,后来在网上看到有人说是 HttpURLConnection 的问题,在此不敢妄加评说,待解决或发现问题后再来解决,同时期待有”知情人士”传教!同时还有一个疑问,网上发现很多人吐槽 HttpURLConnection 问题众多,而且普遍觉得 HttpClient 好用,难道就为了维护方便而剔除 HttpClient 吗?这是不负责任的做法。
Get方式、Post 方式到这里的基本用法也就差不多完了,但实际上 HttpURLConnection 和 HttpClient 在实际使用中各项属性的设置还是有很多学问在里头的,而且二者在使用上优缺点还是比较差异还是比较大的,奈何水平有限,权当扔了一块砖头,看看有没有抛出玉石来
水平有限,如有不足和错误,敬请斧正,_程序猿大人_在此谢过!