认识Http协议
Android中发送http网络请求是很常见的,要有GET请求和POST请求。一个完整的http请求需要经历两个过程:客户端发送请求到服务器,然后服务器将结果返回给客户端,如下图所示:
GET vs POST
Http协议支持的操作有GET、POST、HEAD、PUT、TRACE、OPTIONS、DELETE,其中最最常用的还是GET和POST操作,下面我们看一下GET和POST的区别。
GET:
- GET请求可以被缓存。
- 我们之前提到,当发送键值对信息时,可以在URL上面直接追加键值对参数。当用GET请求发送键值对时,键值对会随着URL一起发送的。
- 由于GET请求发送的键值对时随着URL一起发送的,所以一旦该URL被黑客截获,那么就能看到发送的键值对信息,所以GET请求的安全性很低,不能用GET请求发送敏感的信息(比如用户名密码)。
- 由于URL不能超过2048个字符,所以GET请求发送数据是有长度限制的。
- 由于GET请求较低的安全性,我们不应该用GET请求去执行增加、删除、修改等的操作,应该只用它获取数据。
POST:
- POST请求从不会被缓存。
- POST请求的URL中追加键值对参数,不过这些键值对参数不是随着URL发送的,而是被放入到请求体中发送的,这样安全性稍微好一些。
- 应该用POST请求发送敏感信息,而不是用GET。
- 由于可以在请求体中发送任意的数据,所以理论上POST请求不存在发送数据大小的限制。
- 当执行增减、删除、修改等操作时,应该使用POST请求,而不应该使用GET请求。
HttpURLConnection vs DefaultHttpClient
在Android API Level 9(Android 2.2)之前之能使用DefaultHttpClient类发送http请求。DefaultHttpClient是Apache用于发送http请求的客户端,其提供了强大的API支持,而且基本没有什么bug,但是由于其太过复杂,Android团队在保持向后兼容的情况下,很难对DefaultHttpClient进行增强。为此,Android团队从Android API Level 9开始自己实现了一个发送http请求的客户端类——–HttpURLConnection。
相比于DefaultHttpClient,HttpURLConnection比较轻量级,虽然功能没有DefaultHttpClient那么强大,但是能够满足大部分的需求,所以Android推荐使用HttpURLConnection代替DefaultHttpClient,并不强制使用HttpURLConnection。
但从Android API Level 23(Android 6.0)开始,不能再在Android中使用DefaultHttpClient,强制使用HttpURLConnection。
Demo介绍
为了演示HttpURLConnection的常见用法,我做了一个App,界面如下所示:
主界面MainActivity有四个按钮,分别表示用GET发送请求、用POST发送键值对数据、用POST发送XML数据以及用POST发送JSON数据,点击对应的按钮会启动NetworkActivity并执行相应的操作。
NetworkActivity的源码如下所示,此处先贴出代码,后面会详细说明。
package com.ispring.httpurlconnection;
import android.content.Intent;
import android.content.res.AssetManager;
import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
public class NetworkActivity extends AppCompatActivity {
private NetworkAsyncTask networkAsyncTask = new NetworkAsyncTask();
private TextView tvUrl = null;
private TextView tvRequestHeader = null;
private TextView tvRequestBody = null;
private TextView tvResponseHeader = null;
private TextView tvResponseBody = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_network);
tvUrl = (TextView) findViewById(R.id.tvUrl);
tvRequestHeader = (TextView) findViewById(R.id.tvRequestHeader);
tvRequestBody = (TextView) findViewById(R.id.tvRequestBody);
tvResponseHeader = (TextView) findViewById(R.id.tvResponseHeader);
tvResponseBody = (TextView) findViewById(R.id.tvResponseBody);
Intent intent = getIntent();
if (intent != null && intent.getExtras() != null) {
String networkAction = intent.getStringExtra("action");
networkAsyncTask.execute(networkAction);
}
}
class NetworkAsyncTask extends AsyncTask<String, Integer, Map<String, Object>> {
public static final String NETWORK_GET = "NETWORK_GET";
public static final String NETWORK_POST_KEY_VALUE = "NETWORK_POST_KEY_VALUE";
public static final String NETWORK_POST_XML = "NETWORK_POST_XML";
public static final String NETWORK_POST_JSON = "NETWORK_POST_JSON";
@Override
protected Map<String, Object> doInBackground(String... params) {
Map<String,Object> result = new HashMap<>();
URL url = null;
HttpURLConnection conn = null;
String requestHeader = null;
byte[] requestBody = null;
String responseHeader = null;
byte[] responseBody = null;
String action = params[0];
try {
if (NETWORK_GET.equals(action)) {
url = new URL("http://192.168.31.200:8080/HttpServer/MyServlet?name=孙群&age=27");
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setDoInput(true);
conn.setRequestProperty("action", NETWORK_GET);
conn.setUseCaches(false);
requestHeader = getReqeustHeader(conn);
conn.connect();
InputStream is = conn.getInputStream();
responseBody = getBytesByInputStream(is);
responseHeader = getResponseHeader(conn);
} else if (NETWORK_POST_KEY_VALUE.equals(action)) {
url = new URL("http://192.168.31.200:8080/HttpServer/MyServlet");
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setRequestProperty("action", NETWORK_POST_KEY_VALUE);
requestHeader = getReqeustHeader(conn);
OutputStream os = conn.getOutputStream();
requestBody = new String("name=孙群&age=27").getBytes("UTF-8");
os.write(requestBody);
os.flush();
os.close();
InputStream is = conn.getInputStream();
responseBody = getBytesByInputStream(is);
responseHeader = getResponseHeader(conn);
} else if (NETWORK_POST_XML.equals(action)) {
url = new URL("http://192.168.31.200:8080/HttpServer/MyServlet");
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setRequestProperty("action", NETWORK_POST_XML);
requestHeader = getReqeustHeader(conn);
OutputStream os = conn.getOutputStream();
requestBody = getBytesFromAssets("person.xml");
os.write(requestBody);
os.flush();
os.close();
InputStream is = conn.getInputStream();
responseBody = getBytesByInputStream(is);
responseHeader = getResponseHeader(conn);
} else if (NETWORK_POST_JSON.equals(action)) {
url = new URL("http://192.168.31.200:8080/HttpServer/MyServlet");
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setRequestProperty("action", NETWORK_POST_JSON);
requestHeader = getReqeustHeader(conn);
OutputStream os = conn.getOutputStream();
requestBody = getBytesFromAssets("person.json");
os.write(requestBody);
os.flush();
os.close();
InputStream is = conn.getInputStream();
responseBody = getBytesByInputStream(is);
responseHeader = getResponseHeader(conn);
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (conn != null) {
conn.disconnect();
}
}
result.put("url", url.toString());
result.put("action", action);
result.put("requestHeader", requestHeader);
result.put("requestBody", requestBody);
result.put("responseHeader", responseHeader);
result.put("responseBody", responseBody);
return result;
}
@Override
protected void onPostExecute(Map<String, Object> result) {
super.onPostExecute(result);
String url = (String)result.get("url");
String action = (String) result.get("action");
String requestHeader = (String) result.get("requestHeader");
byte[] requestBody = (byte[]) result.get("requestBody");
String responseHeader = (String) result.get("responseHeader");
byte[] responseBody = (byte[]) result.get("responseBody");
tvUrl.setText(url);
if (requestHeader != null) {
tvRequestHeader.setText(requestHeader);
}
if(requestBody != null){
try{
String request = new String(requestBody, "UTF-8");
tvRequestBody.setText(request);
}catch (UnsupportedEncodingException e){
e.printStackTrace();
}
}
if (responseHeader != null) {
tvResponseHeader.setText(responseHeader);
}
if (NETWORK_GET.equals(action)) {
String response = getStringByBytes(responseBody);
tvResponseBody.setText(response);
} else if (NETWORK_POST_KEY_VALUE.equals(action)) {
String response = getStringByBytes(responseBody);
tvResponseBody.setText(response);
} else if (NETWORK_POST_XML.equals(action)) {
String response = parseXmlResultByBytes(responseBody);
tvResponseBody.setText(response);
} else if (NETWORK_POST_JSON.equals(action)) {
String response = parseJsonResultByBytes(responseBody);
tvResponseBody.setText(response);
}
}
private String getReqeustHeader(HttpURLConnection conn) {
Map<String, List<String>> requestHeaderMap = conn.getRequestProperties();
Iterator<String> requestHeaderIterator = requestHeaderMap.keySet().iterator();
StringBuilder sbRequestHeader = new StringBuilder();
while (requestHeaderIterator.hasNext()) {
String requestHeaderKey = requestHeaderIterator.next();
String requestHeaderValue = conn.getRequestProperty(requestHeaderKey);
sbRequestHeader.append(requestHeaderKey);
sbRequestHeader.append(":");
sbRequestHeader.append(requestHeaderValue);
sbRequestHeader.append("\n");
}
return sbRequestHeader.toString();
}
private String getResponseHeader(HttpURLConnection conn) {
Map<String, List<String>> responseHeaderMap = conn.getHeaderFields();
int size = responseHeaderMap.size();
StringBuilder sbResponseHeader = new StringBuilder();
for(int i = 0; i < size; i++){
String responseHeaderKey = conn.getHeaderFieldKey(i);
String responseHeaderValue = conn.getHeaderField(i);
sbResponseHeader.append(responseHeaderKey);
sbResponseHeader.append(":");
sbResponseHeader.append(responseHeaderValue);
sbResponseHeader.append("\n");
}
return sbResponseHeader.toString();
}
private String getStringByBytes(byte[] bytes) {
String str = "";
try {
str = new String(bytes, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return str;
}
private byte[] getBytesByInputStream(InputStream is) {
byte[] bytes = null;
BufferedInputStream bis = new BufferedInputStream(is);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(baos);
byte[] buffer = new byte[1024 * 8];
int length = 0;
try {
while ((length = bis.read(buffer)) > 0) {
bos.write(buffer, 0, length);
}
bos.flush();
bytes = baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return bytes;
}
private byte[] getBytesFromAssets(String fileName){
byte[] bytes = null;
AssetManager assetManager = getAssets();
InputStream is = null;
try{
is = assetManager.open(fileName);
bytes = getBytesByInputStream(is);
}catch (IOException e){
e.printStackTrace();
}
return bytes;
}
private String parseXmlResultByBytes(byte[] bytes) {
InputStream is = new ByteArrayInputStream(bytes);
StringBuilder sb = new StringBuilder();
List<Person> persons = XmlParser.parse(is);
for (Person person : persons) {
sb.append(person.toString()).append("\n");
}
return sb.toString();
}
private String parseJsonResultByBytes(byte[] bytes){
String jsonString = getStringByBytes(bytes);
List<Person> persons = JsonParser.parse(jsonString);
StringBuilder sb = new StringBuilder();
for (Person person : persons) {
sb.append(person.toString()).append("\n");
}
return sb.toString();
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
- 274
- 275
- 276
- 277
- 278
- 279
- 280
- 281
- 282
- 283
- 284
- 285
- 286
- 287
- 288
- 289
- 290
- 291
- 292
- 293
- 294
- 295
- 296
- 297
- 298
- 299
- 300
- 301
- 302
- 303
- 304
- 305
- 306
- 307
- 308
- 309
- 310
- 311
- 312
- 313
- 314
- 315
- 316
- 317
- 318
- 319
- 320
- 321
- 322
- 323
- 324
- 325
- 326
- 327
- 328
- 329
- 330
- 331
- 332
- 333
- 334
- 335
- 336
- 337
- 338
- 339
- 340
- 341
- 342
- 343
- 344
- 345
- 346
- 347
- 348
- 349
- 350
- 351
- 352
- 353
- 354
- 355
- 356
- 357
- 358
- 359
- 360
- 361
- 362
- 363
- 364
- 365
- 366
- 367
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
- 274
- 275
- 276
- 277
- 278
- 279
- 280
- 281
- 282
- 283
- 284
- 285
- 286
- 287
- 288
- 289
- 290
- 291
- 292
- 293
- 294
- 295
- 296
- 297
- 298
- 299
- 300
- 301
- 302
- 303
- 304
- 305
- 306
- 307
- 308
- 309
- 310
- 311
- 312
- 313
- 314
- 315
- 316
- 317
- 318
- 319
- 320
- 321
- 322
- 323
- 324
- 325
- 326
- 327
- 328
- 329
- 330
- 331
- 332
- 333
- 334
- 335
- 336
- 337
- 338
- 339
- 340
- 341
- 342
- 343
- 344
- 345
- 346
- 347
- 348
- 349
- 350
- 351
- 352
- 353
- 354
- 355
- 356
- 357
- 358
- 359
- 360
- 361
- 362
- 363
- 364
- 365
- 366
- 367
这个App是用来发送http请求的客户端,除此之外,我还创建了一个JSP的WebProject作为服务端,用Servlet对客户端发来的请求进行处理,Servlet的代码如下所示:
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.Enumeration;
@WebServlet(name = "MyServlet")
public class MyServlet extends HttpServlet {
private static final String NETWORK_GET = "NETWORK_GET";
private static final String NETWORK_POST_KEY_VALUE = "NETWORK_POST_KEY_VALUE";
private static final String NETWORK_POST_XML = "NETWORK_POST_XML";
private static final String NETWORK_POST_JSON = "NETWORK_POST_JSON";
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String action = request.getHeader("action");
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/plain;charset=UTF-8");
if(NETWORK_GET.equals(action) || NETWORK_POST_KEY_VALUE.equals(action)){
Enumeration<String> parameterNames = request.getParameterNames();
PrintWriter writer = response.getWriter();
while(parameterNames.hasMoreElements()){
String name = parameterNames.nextElement();
String value = request.getParameter(name);
if(request.getMethod().toUpperCase().equals("GET")){
value = new String(value.getBytes("ISO-8859-1"), "UTF-8");
}
writer.write(name + "=" + value + "\n");
}
writer.flush();
writer.close();
}else if(NETWORK_POST_XML.equals(action) || NETWORK_POST_JSON.equals(action)){
BufferedInputStream bis = new BufferedInputStream(request.getInputStream());
BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream());
byte[] buffer = new byte[1024 * 8];
int length = 0;
while ( (length = bis.read(buffer)) > 0){
bos.write(buffer, 0, length);
}
bos.flush();
bos.close();
bis.close();
}else{
PrintWriter writer = response.getWriter();
writer.write("非法的请求头: action");
writer.flush();
writer.close();
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
发送GET请求
由于网络请求耗时而且会阻塞当前线程,所以我们将发送http请求的操作都放到NetworkAsyncTask中,NetworkAsyncTask是继承自AsyncTask。
点击”GET”按钮后,界面如下所示:
GET请求是最简单的http请求,其发送请求的代码如下所示:
if (NETWORK_GET.equals(action)) {
url = new URL("http://192.168.31.200:8080/HttpServer/MyServlet?name=孙群&age=27");
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setDoInput(true);
conn.setRequestProperty("action", NETWORK_GET);
conn.setUseCaches(false);
requestHeader = getReqeustHeader(conn);
conn.connect();
InputStream is = conn.getInputStream();
responseBody = getBytesByInputStream(is);
responseHeader = getResponseHeader(conn);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
上面的注释写的比较详细了,此处对代码进行一下简单说明。
-
我们在URL的后面添加了?name=孙群&age=27
,这样就相当于添加了两个键值对,其形式如?key1=value1&key2=value2&key3=value3
,在?
后面添加键值对,键和值之间用=
相连,键值对之间用&
分隔,服务端可以读取这些键值对信息。
-
HttpURLConnection默认就是用GET发送请求,当然也可以用conn.setRequestMethod(“GET”)将其显式地设置为GET请求。
-
通过setRequestProperty方法可以设置请求头,既可以是标准的请求头,也可以是自定义的请求头,此处我们设置了自定义的请求头action,用于服务端判断请求的类型。
-
GET请求容易被缓存,我们可以用conn.setUseCaches(false)禁用缓存。
-
通过调用connect方法可以让客户端和服务器之间建立TCP连接,建立连接之后不会立即传输数据,只是表示处于connected状态了。该方法不必显式调用,因为在之后的getInputStream方法中会隐式地调用该方法。
-
调用HttpURLConnection的getInputStream()方法可以获得响应结果的输入流,即服务器向客户端输出的信息,需要注意的是,getInputStream()方法的调用必须在一系列的set方法之后进行。
-
然后在方法getBytesByInputStream中,通过输入流的read方法得到字节数组,read方法是阻塞式的,每read一次,其实就是从服务器上下载一部分数据,直到将服务器的输出全部下载完成,这样就得到响应体responseBody了。
-
我们可以通过getReqeustHeader方法读取请求头,代码如下所示:
private String getReqeustHeader(HttpURLConnection conn) {
Map<String, List<String>> requestHeaderMap = conn.getRequestProperties();
Iterator<String> requestHeaderIterator = requestHeaderMap.keySet().iterator();
StringBuilder sbRequestHeader = new StringBuilder();
while (requestHeaderIterator.hasNext()) {
String requestHeaderKey = requestHeaderIterator.next();
String requestHeaderValue = conn.getRequestProperty(requestHeaderKey);
sbRequestHeader.append(requestHeaderKey);
sbRequestHeader.append(":");
sbRequestHeader.append(requestHeaderValue);
sbRequestHeader.append("\n");
}
return sbRequestHeader.toString();
}
由上可以看出,以上方法主要还是调用了HttpURLConnection的方法getRequestProperty获取请求头,需要注意的是getRequestProperty方法执行时,客户端和服务器之间必须还未建立TCP连接,即还没有调用connect方法,在connected之后执行getRequestProperty会抛出异常,详见https://github.com/square/okhttp/blob/master/okhttp-urlconnection/src/main/java/okhttp3/internal/huc/HttpURLConnectionImpl.java#L236
-
我们还可以通过getResponseHeader方法获取响应头,代码如下所示:
private String getResponseHeader(HttpURLConnection conn) {
Map<String, List<String>> responseHeaderMap = conn.getHeaderFields();
int size = responseHeaderMap.size();
StringBuilder sbResponseHeader = new StringBuilder();
for(int i = 0; i < size; i++){
String responseHeaderKey = conn.getHeaderFieldKey(i);
String responseHeaderValue = conn.getHeaderField(i);
sbResponseHeader.append(responseHeaderKey);
sbResponseHeader.append(":");
sbResponseHeader.append(responseHeaderValue);
sbResponseHeader.append("\n");
}
return sbResponseHeader.toString();
}
通过方法getHeaderFieldKey可以获得响应头的key值,通过方法getHeaderField可以获得响应头的value值。
在后台的Servelt中将键值对信息重新原样写入到客户端,服务端的代码如下所示:
if(NETWORK_GET.equals(action) || NETWORK_POST_KEY_VALUE.equals(action)){
//对于NETWORK_GET和NETWORK_POST_KEY_VALUE,遍历键值对,并将键值对重新写回到输出结果中
Enumeration<String> parameterNames = request.getParameterNames()
PrintWriter writer = response.getWriter()
while(parameterNames.hasMoreElements()){
String name = parameterNames.nextElement()
String value = request.getParameter(name)
if(request.getMethod().toUpperCase().equals("GET")){
//GET请求需要进行编码转换,POST不需要
value = new String(value.getBytes("ISO-8859-1"), "UTF-8")
}
writer.write(name + "=" + value + "\n")
}
writer.flush()
writer.close()
}
在接收到服务端返回的数据后,在AsyncTask的onPostExecute方法中会将Url、请求头、响应头、响应体的值展示在UI上。
用POST发送键值对数据
点击”POST KEY VALUE”按钮,可以用POST发送键值对数据,界面如下所示:
代码如下所示:
if (NETWORK_POST_KEY_VALUE.equals(action)) {
url = new URL("http://192.168.31.200:8080/HttpServer/MyServlet");
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setRequestProperty("action", NETWORK_POST_KEY_VALUE);
requestHeader = getReqeustHeader(conn);
OutputStream os = conn.getOutputStream();
requestBody = new String("name=孙群&age=27").getBytes("UTF-8");
os.write(requestBody);
os.flush();
os.close();
InputStream is = conn.getInputStream();
responseBody = getBytesByInputStream(is);
responseHeader = getResponseHeader(conn);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
使用POST发送请求的代码与用GET发送请求的代码大部分类似,我们只对其中不同的地方做下说明。
-
需要通过setRequestMethod将conn设置成POST方法。
-
如果想用POST发送请求体,那么需要调用setDoOutput方法,将其设置为true。
-
通过conn.getOutputStream()获得输出流,可以向输出流中写入请求体,最后记得调用输出流的flush方法,注意此时并没有真正将请求体发送到服务器端。
-
当调用getInputStream方法后,才真正将请求体的内容发送到服务器。
在我们的服务器端的Servlet中,在接收到POST请求发送的键值对数据后,也只是简单地将键值对数据原样写入给客户端,具体代码参见上文,不再赘述。
用POST发送XML数据
点击”POST XML”按钮,可以用POST发送XML数据,界面如下所示:
代码如下所示:
if (NETWORK_POST_XML.equals(action)) {
url = new URL("http://192.168.31.200:8080/HttpServer/MyServlet");
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setRequestProperty("action", NETWORK_POST_XML);
requestHeader = getReqeustHeader(conn);
OutputStream os = conn.getOutputStream();
requestBody = getBytesFromAssets("person.xml");
os.write(requestBody);
os.flush();
os.close();
InputStream is = conn.getInputStream();
responseBody = getBytesByInputStream(is);
responseHeader = getResponseHeader(conn);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
上面的代码与用POST发送键值对的代码很相似,对其进行简单说明。
-
上述代码通过getBytesFromAssets方法读取了assets目录下的person.xml文件,将xml文件的字节流作为请求体requestBody,然后将该请求体发送到服务器。
-
person.xml文件如下所示:
<?xml version="1.0" encoding="utf-8"?>
<persons>
<person id="101">
<name>张三</name>
<age>27</age>
</person>
<person id="102">
<name>李四</name>
<age>28</age>
</person>
</persons>
<person>
标签对应着Person
类,Person
类代码如下所示:
package com.ispring.httpurlconnection;
public class Person {
private String id = "";
private String name = "";
private int age = 0;
public String getId(){
return id;
}
public void setId(String id){
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return new StringBuilder().append("name:").append(getName()).append(", age:").append(getAge()).toString();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
-
服务端接收到客户端发送来的XML数据之后,只是简单的将其原样写回到客户端,即客户端接收到的响应体还是原来的XML数据,然后客户端通过parseXmlResultByBytes方法对该XML数据进行解析,将字节数组转换成List<Person>
,parseXmlResultByBytes代码如下所示:
private String parseXmlResultByBytes(byte[] bytes) {
InputStream is = new ByteArrayInputStream(bytes);
StringBuilder sb = new StringBuilder();
List<Person> persons = XmlParser.parse(is);
for (Person person : persons) {
sb.append(person.toString()).append("\n");
}
return sb.toString();
}
该方法使用了自定义的XmlParser类对XML数据进行解析,其源码如下所示:
package com.ispring.httpurlconnection;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
public class XmlParser {
public static List<Person> parse(InputStream is) {
List<Person> persons = new ArrayList<>();
try{
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
PersonHandler personHandler = new PersonHandler();
parser.parse(is, personHandler);
persons = personHandler.getPersons();
}catch (Exception e){
e.printStackTrace();
}
return persons;
}
static class PersonHandler extends DefaultHandler {
private List<Person> persons;
private Person temp;
private StringBuilder sb;
public List<Person> getPersons(){
return persons;
}
@Override
public void startDocument() throws SAXException {
super.startDocument();
persons = new ArrayList<>();
sb = new StringBuilder();
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
super.startElement(uri, localName, qName, attributes);
sb.setLength(0);
if(localName.equals("person")){
temp = new Person();
int length = attributes.getLength();
for(int i = 0; i < length; i++){
String name = attributes.getLocalName(i);
if(name.equals("id")){
String value = attributes.getValue(i);
temp.setId(value);
}
}
}
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
super.characters(ch, start, length);
sb.append(ch, start, length);
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
super.endElement(uri, localName, qName);
if(localName.equals("name")){
String name = sb.toString();
temp.setName(name);
}else if(localName.equals("age")){
int age = Integer.parseInt(sb.toString());
temp.setAge(age);
}else if(localName.equals("person")){
persons.add(temp);
}
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
用POST发送JSON数据
点击”POST JSON”按钮,可以用POST发送JSON数据,界面如下所示:
代码如下所示:
if (NETWORK_POST_JSON.equals(action)) {
url = new URL("http://192.168.31.200:8080/HttpServer/MyServlet");
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setRequestProperty("action", NETWORK_POST_JSON);
requestHeader = getReqeustHeader(conn);
OutputStream os = conn.getOutputStream();
requestBody = getBytesFromAssets("person.json");
os.write(requestBody);
os.flush();
os.close();
InputStream is = conn.getInputStream();
responseBody = getBytesByInputStream(is);
responseHeader = getResponseHeader(conn);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
上面的代码与用POST发送XML的代码很相似,对其进行简单说明。
- 上述代码通过getBytesFromAssets方法读取了assets目录下的person.json文件,将json文件的字节流作为请求体requestBody,然后将该请求体发送到服务器。
-
person.json文件如下所示:
{
"persons": [{
"id": "101",
"name":"张三",
"age":27
}, {
"id": "102",
"name":"李四",
"age":28
}]
}
persons数组中的每个元素都对应着一个Person
对象。
-
服务端接收到客户端发送来的JSON数据之后,只是简单的将其原样写回到客户端,即客户端接收到的响应体还是原来的JSON数据,然后客户端通过parseJsonResultByBytes方法对该XML数据进行解析,将字节数组转换成List,parseXmlResultByBytes代码如下所示:
private String parseJsonResultByBytes(byte[] bytes){
String jsonString = getStringByBytes(bytes);
List<Person> persons = JsonParser.parse(jsonString);
StringBuilder sb = new StringBuilder();
for (Person person : persons) {
sb.append(person.toString()).append("\n");
}
return sb.toString();
}
该方法又使用了自定义的JsonParset类,源码如下所示:
package com.ispring.httpurlconnection;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
public class JsonParser {
public static List<Person> parse(String jsonString){
List<Person> persons = new ArrayList<>();
try{
JSONObject jsonObject = new JSONObject(jsonString);
JSONArray jsonArray = jsonObject.getJSONArray("persons");
int length = jsonArray.length();
for(int i = 0; i < length; i++){
JSONObject personObject = jsonArray.getJSONObject(i);
String id = personObject.getString("id");
String name = personObject.getString("name");
int age = personObject.getInt("age");
Person person = new Person();
person.setId(id);
person.setName(name);
person.setAge(age);
persons.add(person);
}
}catch (JSONException e){
e.printStackTrace();
}
return persons;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
其他
-
如果Http请求体的数据很大,就可以认为该请求主要是完成数据上传的作用;如果响应体的数据很大,就可以认为该请求主要完成数据下载的作用。
-
上面我们通过demo演示了如何上传XML文件和JSON文件,并对二者进行解析。在上传的过程中,Android要写入Content-Length这个请求头,Content-Length就是请求体的字节长度,注意是字节长度,而不是字符长度(汉字等会占用两个字节)。默认情况下,Android为了得到Content-Length的长度,Android会把请求体放到内存中的,直到输出流调用了close方法后,才会读取内存中请求体的字节长度,将其作为请求头Content-Length。当要上传的请求体很大时,这会非常占用内存,为此Android提供了两个方法来解决这个问题。
-
setFixedLengthStreamingMode (int contentLength)
如果请求体的大小是知道的,那么可以调用HttpURLConnection的setFixedLengthStreamingMode (int contentLength) 方法,该方法会告诉Android要传输的请求头Content-Length的大小,这样Android就无需读取整个请求体的大小,从而不必一下将请求体全部放到内存中,这样就避免了请求体占用巨大内存的问题。
-
setChunkedStreamingMode (int chunkLength)
如果请求体的大小不知道,那么可以调用setChunkedStreamingMode (int chunkLength)方法。该方法将传输的请求体分块传输,即将原始的数据分成多个数据块,chunkLength表示每块传输的字节大小。比如我们要传输的请求体大小是10M,我们将chunkLength设置为1024 * 1024 byte,即1M,那么Android会将请求体分10次传输,每次传输1M,具体的传输规则是:每次传输一个数据块时,首先在一行中写明该数据块的长度,比如1024 * 1024,然后在后面的一行中写入要传输的数据块的字节数组,再然后是一个空白行,这样第一数据块就这样传输,在空白行之后就是第二个数据块的传输,与第一个数据块的格式一样,直到最后没有数据块要传输了,就在用一行写明要传输的字节为0,这样在服务器端就知道读取完了整个请求体了。
如果设置的chunkLength的值为0,那么表示Android会使用默认的一个值作为实际的chunkLength。
使用setChunkedStreamingMode方法的前提是服务器支持分块数据传输,分块数据传输是从HTTP 1.1开始支持的,所以如果你的服务器只支持HTTP 1.0的话,那么不能使用setChunkedStreamingMode方法。