经常使用
HttpURLConncetion
进行网络请求,也遇到了一些坑,这里总结一下,方便以后查阅。以后遇到了新的坑,也会在后面进行补充。
使用HttpUrlConnection进行网络请求时,如果不调用HttpUrlConnection
的getInputStream()
,getResponseCode()
,getContent()
等方法方法时,请求不会发生,即便调用了HttpUrlConnection
的connect()
方法,请求还是不会发生(这里需要看看HttpURLConnection
的具体源码实现).eg:
public void get(String urlStr) {
HttpURLConnection connection = null;
try {
URL url = new URL(urlStr);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
connection.connect();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
上面的方法被调用时,请求并不会发生.
虽然设置了HttpUrlConnection
的请求方法为GET,即connection.setRequestMethod("GET")
但是同时又调用了setDoOutput(true)
,此时该请求将会自动转为POST请求(GET方法请求时不能携带输出流,所以自动转为POST方法?),eg:
public String get(String urlStr) {
HttpURLConnection connection = null;
BufferedReader reader = null;
try {
URL url = new URL(urlStr);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
connection.setDoOutput(true);
reader=new BufferedReader(new InputStreamReader(connection.getInputStream(),"UTF-8"));
StringBuilder sb = new StringBuilder();
String line;
while ((line=reader.readLine())!=null){
sb.append(line);
}
return sb.toString();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.disconnect();
}
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return "";
}
上述的GET请求会自动转变为POST请求.
即便在服务端设置了返回数据的编码,但是有可能还是乱码,不管是调用了resp.setContentType("text/html; charset=UTF-8")
还是resp.setCharacterEncoding("UTF-8")
都还是乱码(这里假设UTF-8位正确的编码).有可能你在设置编码之前,直接或者间接的获取了HttpServletResponse字符流的操作,所以只需要在最开始的时候设置编码即可,eg:
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().println("到底是不是乱码?");
resp.setContentType("text/html; charset=UTF-8");
resp.setCharacterEncoding("UTF-8");
}
上述代码,给客户端返回的还是乱码,需要将编码设置放在前面,如下:
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setCharacterEncoding("UTF-8");
resp.getWriter().println("到底是不是乱码?");
}
这样就可以让乱码变成高清无码了, 为什么会这样呢,先看看下面的代码,或许能解释原因,这里假设我们要将一些包含中文的数据保存到文件中:
public void save(File file,String data){
FileOutputStream fos=null;
BufferedWriter writer=null;
try {
fos=new FileOutputStream(file);
//注意这里设置了字符编码
writer=new BufferedWriter(new OutputStreamWriter(fos,"UTF-8"));
writer.write(data);
writer.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fos!=null){
fos.close();
}
if (writer!=null){
writer.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
我们注意到,当从字节流转换为字符流的时候,我们可以设置字符流编码,然而字符流对象(xxxWriter)仅仅提供了getEncoding()方法,并没有提供setEncoding()方法,所以一旦new出了一个字符流对象(xxxWriter),后面就没有办法在设置编码了,所以上面服务端返回的乱码的问题就能很好解释了.
我们先看一下一段服务端的代码,改代码实现的功能是客户端上传文件到服务端,同时在url中给出上传文件的文件名.
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String filename=req.getParameter("filename");
InputStream is=req.getInputStream();
//此时is.available()方法有可能会返回0
is.available()
//保存文件方法略
saveFile(filename,is);
}
当上传的文件比较大时(我测试的文件有几十MB),上面的代码将会正常工作,但是上传的文件比较小时(我测试的文件只有几字节的文本),上述代码始终接收到0字节的文件,开始以为客户端出了问题,经过调试以后,发现客户端代码正常,flush()方法也正常调用了.就只剩下服务端的问题了,发现is.available()方法返回值一直是0,不知道这部字节流数据为什么消失了.
最后网上查了一下资料,发现这部分数据并不是消失了,而是跑到HttpServletRequest的getParameterMap()方法返回的Map中去了,这是因为受了HttpServletRequest.getParameter()相关方法的影响,输出这个map来看,发现文件中所存储的数据变成了这个map中的一个key值,(这里还需要查资料,为什么会这样?).
解决方案也很简单,在getParameter()及相关方法之前获取字节输入流即可,代码如下:
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
InputStream is=req.getInputStream();
String filename=req.getParameter("filename");
//保存文件方法略
saveFile(filename,is);
}
我这里使用的是HttpURLConnection进行文件的上传,经测试,同事使用Retrofit2上传大文件也会导致OOM。
原因:HTTPURLConnection和Retrofit2底层都是使用的okio作为输入输出流,它默认情况下会使用内部缓存,当上传的文件过大时,随着流的不断写入,缓存不断增多,当超过当前进程所允许的最大内存时,就会导致OOM。
解决办法也很直接,不使用缓存或者限制缓存的使用就可以了,查了一些资料,有两种解决方法。
1. 使用connection.setChunkedStreamingMode(0);
,该方法的意思是:当传输的内容长度未知时,不使用内部缓存,进行分块传输(这需要服务端可以支持,tomcat默认支持)当传入的参数小于等于0时,默认分块大小为4096 bytes,当然也可以指定分块大小,比如:connection.setChunkedStreamingMode(2048);
2. 使用connection.setFixedLengthStreamingMode((int)file.length());
,该方法的意思是:当传输的内容长度已知时,不使用内部缓存,进行流传输。
这两个方法的更详细的解释可以参考HttpURLConnection源码的相关注释。
持续更新中…