JDK 中的 URLConnection 详解

JDK 的 URLConnection 类详解

1. URL 请求

        URL 请求分为两类: GET 请求与 POST 请求, 两者的区别在于:

        a) get 请求可以获取静态页面, 也可以把参数放在 URL 字串后面传递给 Servlet

        b) get 请求方式的参数是放在 URL 字串里; 而 post 请求方式的参数是放在 http 请求的正文里

2. 在 URL 上调用 openConnection 方法创建连接对象

URL url = new URL("http://localhost:8080/TestHttpURLConnection/index.jsp");

URLConnection urlConnection = url.openConnection();

//
此处的 urlConnection 对象实际上是根据 URL 的请求协议(此处是http)所生成的 URLConnection 类的子类 HttpURLConnection.
// 所以,
此处最好将其转化为 HttpURLConnection 类型的对象, 以便用到 HttpURLConnection 更多的API
HttpURLConnection httpUrlConnection = (HttpURLConnection) urlConnection;
// 也可用一句代码代替
// HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection();

3. 设置 HttpURLConnection 对象的参数和一般请求属性

// 设置是否要向 httpUrlConnection 对象输出数据, 默认为 false
// 即: 如果需要向输出流中输出数据则设为 true
// 因为这是个POST请求, 而POST的请求参数要放在HTTP的正文里, 所以需要向输出流中输出数据, 所以设置成 true.
httpUrlConnection.setDoOutput(true);


// 设置是否要从 httpUrlConnection 对象中读取数据, 默认为 true
httpUrlConnection.setDoInput(true);


// 设置不使用缓存(POST请求不能使用缓存)
httpUrlConnection.setUseCaches(false);


// 设置要发送的数据的内容类型是可序列化的 Java 对象
// 如果不设此项, 那么在传送序列化对象时, 当 WEB 服务默认的不是这种类型时可能会抛 java.io.EOFException
httpUrlConnection.setRequestProperty("Content-type", "application/x-java-serialized-object");


// 设置请求方式为 POST, 默认为 GET 方式
httpUrlConnection.setRequestMethod("POST");


// 使用 connect 方法建立到远程对象的实际连接
//从第2条中 url.openConnection() 至此的设置必须要在 connect 之前完成
//还可以设置诸如连接超时时间等参数 
httpUrlConnection.connect(); 

4. HttpURLConnection 的连接细节

// 因为在调用getOutputStream方法时会隐式的进行connect操作,所以在开发中不显式调用上述的connect()也可以 
OutputStream outStream = httpUrlConnection.getOutputStream();

5. HttpURLConnection 写数据与发送数据

// 通过"输出流对象"构建"对象输出流对象", 以实现输出可序列化的对象
ObjectOutputStream objOutputStream = new ObjectOutputStream(outStream);


// 向"对象输出流"写数据, 这些数据将保存到内存里的缓冲区中
objOutputStream.writeObject(new String("我是测试数据"));


// 刷新"对象输出流", 将所有字节都写入潜在的流中(此处为ObjectOutputStream
objOutputSteam.flush();


// 关闭流对象, 此时不能再向"对象输出流"写入任何数据, 先前写入的数据存在于内存缓冲区中, 
// 在调用下面的 getInputStream() 方法时才把准备好的 http 请求正式发送到服务器 
objOutputStm.close();
// 调用 HttpURLConnection 连接对象的 getInputStream() 方法, 
// 将内存缓冲区中封装好的完整的 HTTP 请求正文发送到服务端
InputStream inStream = httpUrlConnection.getInputStream(); // <-- 注意! 实际发送请求的代码段在这里

// 上面的 httpUrlConnection.getInputStream() 方法已调用, 本次HTTP请求已结束,下面向对象输出流的输出已无意义,
// 既使对象输出流没有调用 close() 方法, 下面的操作也不会向对象输出流写入任何数据.
// 因此, 要重新发送数据时需要重新创建连接、重新设参数、重新创建流对象、重新写数据、重新发送数据(至于是否不用重新这些操作需要再研究)
objOutputSteam.writeObject(new String(""));

httpUrlConnection.getInputStream();

总结:
 a) HttpURLConnection 的 connect() 方法实际上只是建立了一个与服务器的 tcp 连接, 并没有实际发送 http 请求. 无论是 post 还是 get, http 请求实际上直到 HttpURLConnection 的 getInputStream() 这个方法里面才正式发送出去.

b) 在用 POST 方式发送 URL 请求时, URL 请求参数的设定顺序是重中之重, 对 connection 对象的一切配置(那一堆 set 方法)都必须要在 connect() 方法执行之前完成, 而对 outputStream 的写操作又必须要在 inputStream 的读操作之前. 这些顺序是由 http 请求的格式决定的. 如果 InputStream 读操作在 OutputStream 的写操作之前则会抛出异常: java.net.ProtocolException: Cannot write output after reading input....... 

c) http 请求实际上由两部分组成, 
        一个是 http 头, 所有关于此次 http 请求的配置都在 http 头里面定义.
        一个是正文 content, connect() 方法会根据 HttpURLConnection 对象的配置值生成 http 头部信息, 因此在调用connect函数之前就必须把所有的配置准备好.

d) 在 http 头后面紧跟着的是 http 请求的正文, 正文的内容是通过 outputStream 流写入的, 实际上 outputStream 不是一个网络流, 充其量是个字符串流, 往里面写入的东西不会立即发送到网络, 而是存储在内存缓冲区中, 待 outputStream 流关闭时根据输入的内容生成 http 正文. 
        至此 http 请求的东西已经全部准备就绪。在 getInputStream() 方法调用的时候就会把准备好的 http 请求正式发送给服务器了, 然后返回一个输入流, 用于读取服务器对此次 http 请求的返回信息.
        由于 http 请求在 getInputStream 的时候已经发送出去了(包括 http 头和正文)因此在 getInputStream() 方法之后对 connection 对象进行设置(对 http 头的信息进行修改)或者写入outputStream(对正文进行修改)都是没有意义的了, 执行这些操作会导致异常的发生.

 6. Servlet 端开发注意点 
a) 对于客户端发送的 POST 类型的 HTTP 请求 Servlet 必须实现 doPost 方法而不能用doGet方法.
b) 用 HttpServletRequest 的 getInputStream() 方法取得 InputStream的对象 
     InputStream inStream = httpRequest.getInputStream(); 
     现在调用 inStream.available()(该方法用于"返回此输入流下一个方法调用可以不受阻塞地从此输入流读取(或跳过)的估计字节数")时,
     永远都返回0, 试图使用此方法的返回值分配缓冲区以保存此流所有数据的做法是不正确的. 那么现在的解决办法是 Servlet 这一端用如下实现: 
     InputStream inStream = httpRequest.getInputStream(); 
     ObjectInputStream objInStream = new ObjectInputStream(inStream); 
     Object obj = objInStream.readObject(); 
     // 做后续的处理 
     // 。。。。。。 
     // 。。。 。。。 
     而客户端,无论是否发送实际数据都要写入一个对象(那怕这个对象不用), 如: 
     ObjectOutputStream objOutputStream = new ObjectOutputStream(outStream); 
     objOutputStream.writeObject(new String("")); // 这里发送一个空数据 
     // 甚至可以发一个null对象,服务端取到后再做判断处理. 
     objOutputStream.writeObject(null); 
     objOutputStream.flush(); 
     objOutputStream.close(); 

注意:上述在创建对象输出流 ObjectOutputStream时, 如果将从 HttpServletRequest 取得的输入流 (即: new ObjectOutputStream(outStream) 中的outStream) 包装在 BufferedOutputStream 流里面, 则必须有objOutputStream.flush(); 这一句, 以便将流信息刷入缓冲输出流. 如下: 
      ObjectOutputStream objOutputStream = new ObjectOutputStream(new BufferedOutputStream(outStream)); 
      objOutputStream.writeObject(null); 
      objOutputStream.flush(); // <======此处必须要有. 
      objOutputStream.close(); 

HttpURLConnection 是基于 HTTP 协议的, 其底层通过 socket 通信实现. 如果不设置超时(timeout), 在网络异常的情况下, 可能会导致程序僵死而不继续往下执行. 可以通过以下两个语句来设置相应的超时:
System.setProperty("sun.net.client.defaultConnectTimeout", 超时毫秒数字符串);
System.setProperty("sun.net.client.defaultReadTimeout", 超时毫秒数字符串);

其中: sun.net.client.defaultConnectTimeout:连接主机的超时时间(单位:毫秒)
sun.net.client.defaultReadTimeout:从主机读取数据的超时时间(单位:毫秒)

例如:
System.setProperty("sun.net.client.defaultConnectTimeout", "30000");
System.setProperty("sun.net.client.defaultReadTimeout", "30000");

JDK 1.5以前的版本,只能通过设置这两个系统属性来控制网络超时. 在 1.5 中还可以使用 HttpURLConnection 的父类 URLConnection 的以下两个方法:
setConnectTimeout:设置连接主机超时(单位:毫秒)
setReadTimeout:设置从主机读取数据超时(单位:毫秒)

例如:

HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setConnectTimeout(30000);
conn.setReadTimeout(30000);

需要注意的是,在JDK1.4.2环境下,发现在设置了defaultReadTimeout的情况下,如果发生网络超时,HttpURLConnection会自动重新提交一次请求,出现一次请求调用,请求服务器两次的问题(Trouble)。我认为这是JDK1.4.2的一个bug。在JDK1.5.0中,此问题已得到解决,不存在自动重发现象。
建议使用 JDK 1.5 之后的方法.

转自: http://www.blogjava.net/supercrsky/articles/247449.html

http://wangfc123.blog.163.com/blog/static/1574680120091130113145909/

经过整理与修改.

原文地址:https://www.cnblogs.com/xpxpxp2046/p/2290830.html