【Stackoverflow好问题】如何使用java.net.URLConnection收发HTTP请求

问题
如何使用java.net.URLConnection收发HTTP请求呢?处理Http请求,有哪些最佳实践?

讨论:

精华回答


首先声明,下面的代码,都是基本的例子。更严谨的话,还应加入处理各种异常的代码(如IOExceptions、NullPointerException、ArrayIndexOutOfBoundsException)


准备
首先,需要设置请求的URL以及charset(编码);另外还需要哪些参数,则取决于各自url的要求。
String url = "http://example.com";
String charset = "UTF-8";
String param1 = "value1";
String param2 = "value2";
// ...
String query = String.format("param1=%s¶m2=%s", 
URLEncoder.encode(param1, charset), 
URLEncoder.encode(param2, charset));
请求参数必须是name=value这样的格式,每个参数间用&连接。一般来说,你还得用  URLEncoder#encode()对参数做 编码
上面例子还用到了String#format(),这只是为了方便,我更喜欢用这个方式来完成string的拼接。


发送一个HTTP GET请求(可选:带上参数)
这依然是个繁琐的事情。默认的方式如下:
URLConnection connection = new URL(url + "?" + query).openConnection();
connection.setRequestProperty("Accept-Charset", charset);
InputStream response = connection.getInputStream();
// ...
url和参数之间,要用?号连接。请求头(header)中的 Accept-Charset,用于告诉服务器,你所发送参数的编码。如果你不发送任何参数,也可以不管Accept-Charset。如果你无需设置任何header,也可以用 URL#openStream() 而非openConnection。
不管那种方式,假设服务器端是  HttpServlet,那么你的get请求将会触发它的doGet()方法,它能通过 HttpServletRequest#getParameter()获取你传递的参数。


发送一个HTTP POST请求,并带上参数
设置 URLConnection#setDoOutput(),等于隐式地将请求方法设为POST。标准的HTTP POST 表单,其Content-Tyep为application/x-www-form-urlencoded,请求的内容放到到body中。也就是如下代码:
URLConnection connection = new URL(url).openConnection();
connection.setDoOutput(true); // Triggers POST.
connection.setRequestProperty("Accept-Charset", charset);
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=" + charset);

try (OutputStream output = connection.getOutputStream()) {
    output.write(query.getBytes(charset));
}

InputStream response = connection.getInputStream();
// ...
提醒:
当你要提交一个HTML表单时,务必要把<input type="hidden"这类元素的值,以name=value的形式也一并提交。另外,还有<input type="submit">这类元素,也是如此。因为,通常服务端也需要这个信息,来确认哪一个按钮触发了这个提交动作。

也可以使用 HttpURLConnection 来代替 URLConnection ,然后调用 HttpURLConnection#setRequestMethod()来将请求设为POST类型。
HttpURLConnection httpConnection = (HttpURLConnection) new URL(url).openConnection();
httpConnection.setRequestMethod("POST");
// ...
同样的,如果服务端是 HttpServlet,将会触发它的 doPost()方法,可以通过 HttpServletRequest#getParameter()获取post参数


真正触发HTTP请求的发送
你可以显式地通过 URLConnection#connect()来发送请求,但是,当你调用获取响应信息的方法时,一样将自动发送请求。例如当你使用 URLConnection#getInputStream()时,就会自动触发请求,因此,不用多次一举地调用connect()方法。上面我的例子,也都是直接调用getInputStream()方法。


获取HTTP响应信息
1)HTTP响应码:
首先默认你使用了  HttpURLConnection
int status = httpConnection.getResponseCode();
2)HTTP 响应头(headers)
</pre><pre name="code" class="java">for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
    System.out.println(header.getKey() + "=" + header.getValue());
}
3)HTTP响应编码:
当Content-Type中包含charset参数时,说明响应内容是基于charset参数指定的编码。因此,解码响应信息时,也要按照这个编码格式来。
String contentType = connection.getHeaderField("Content-Type");
String charset = null;

for (String param : contentType.replace(" ", "").split(";")) {
    if (param.startsWith("charset=")) {
        charset = param.split("=", 2)[1];
        break;
    }
}

if (charset != null) {
    try (BufferedReader reader = new BufferedReader(new InputStreamReader(response, charset))) {
        for (String line; (line = reader.readLine()) != null;) {
            // ... System.out.println(line) ?
        }
    }
}
else {
    // It's likely binary content, use InputStream/OutputStream.
}



session的维护
服务端session,通常是基于cookie实现的。你可以通过 CookieHandlerAPI来管理cookie。在发送HTTP请求前,初始化一个 CookieManager, 然后设置参数为 CookiePolicy. ACCEPT_ALL
// First set the default cookie manager.
CookieHandler.setDefault(new CookieManager(null, CookiePolicy.ACCEPT_ALL));
// All the following subsequent URLConnections will use the same cookie manager.
URLConnection connection = new URL(url).openConnection();
// ...
connection = new URL(url).openConnection();
// ...
connection = new URL(url).openConnection();
// ...

请注意,这个方式并非适用于所有场景。如果使用这个方式失败了,你可以尝试自己设置cookie:你需要从响应头中拿到Set-Cookie参数,然后再把cookie设置到接下来的其他请求中。
// Gather all cookies on the first request.
URLConnection connection = new URL(url).openConnection();
List<String> cookies = connection.getHeaderFields().get("Set-Cookie");
// ...

// Then use the same cookies on all subsequent requests.
connection = new URL(url).openConnection();
for (String cookie : cookies) {
    connection.addRequestProperty("Cookie", cookie.split(";", 2)[0]);
}
// ...
上面的split(";", 2)[0],作用是去掉一些跟服务端无关的cookie信息(例如expores,path等)。也可用cookie.substring(0, cookie.indexOf(';'))实现同样的目的


流的处理
不管你是否通过connection.setRequestProperty("Content-Length", contentLength)为content设置了定长,   HttpURLConnection在发送请求前,默认都会缓存整个请求的body。如果发送一个比较大的post请求(例如上传文件),有可能会导致OutOfMemoryException。为了避免这个问题,可以设置 HttpURLConnection#setFixedLengthStreamingMode()
httpConnection.setFixedLengthStreamingMode(contentLength);
但如果content长度是未知的,则可以用 HttpURLConnection#setChunkedStreamingMode()。这样,header中Transfer-Encoding会变成chunked,你的请求将会分块发送,例如下面的例子,请求的body,将会按1KB一块,分块发送
httpConnection.setChunkedStreamingMode(1024);


User-Agent
有时候,你发送的请求,可能只有在浏览器下才能正常返回,而其他方式却不行。这可能跟请求头中的User-Agent有关。通过URLConnection发送的请求,默认会带上的User-Agent信息是Java/1.6.0_19,也就是java+jre的版本。你可以重写这个信息:
connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.3) Gecko/20100401"); // Do as if you're using Firefox 3.6.3.
这里有一份更全的浏览器 User-Agent清单


错误处理
如果HTTP的响应码是4xx(客户端异常)或者5xx(服务端异常),你可以通过HttpURLConnection#getErrorStream()获取信息,服务端可能会将一些有用的错误信息放到这里面。
InputStream error = ((HttpURLConnection) connection).getErrorStream();
(译注:没看明白这段话想表达的意思)
If the HTTP response code is -1, then something went wrong with connection and response handling. The HttpURLConnection implementation is somewhat buggy with keeping connections alive. You may want to turn it off by setting the http.keepAlive system property to false. You can do this programmatically in the beginning of your application by:
System.setProperty("http.keepAlive", "false");


上传文件
一般来说,你需要将post的内容设为 multipart/form-data(相关的RFC文档: RFC2388)
String param = "value";
File textFile = new File("/path/to/file.txt");
File binaryFile = new File("/path/to/file.bin");
String boundary = Long.toHexString(System.currentTimeMillis()); // Just generate some unique random value.
String CRLF = "\r\n"; // Line separator required by multipart/form-data.
URLConnection connection = new URL(url).openConnection();
connection.setDoOutput(true);
connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);

try (
    OutputStream output = connection.getOutputStream();
    PrintWriter writer = new PrintWriter(new OutputStreamWriter(output, charset), true);
) {
    // Send normal param.
    writer.append("--" + boundary).append(CRLF);
    writer.append("Content-Disposition: form-data; name=\"param\"").append(CRLF);
    writer.append("Content-Type: text/plain; charset=" + charset).append(CRLF);
    writer.append(CRLF).append(param).append(CRLF).flush();

    // Send text file.
    writer.append("--" + boundary).append(CRLF);
    writer.append("Content-Disposition: form-data; name=\"textFile\"; filename=\"" + textFile.getName() + "\"").append(CRLF);
    writer.append("Content-Type: text/plain; charset=" + charset).append(CRLF); // Text file itself must be saved in this charset!
    writer.append(CRLF).flush();
    Files.copy(textFile.toPath(), output);
    output.flush(); // Important before continuing with writer!
    writer.append(CRLF).flush(); // CRLF is important! It indicates end of boundary.

    // Send binary file.
    writer.append("--" + boundary).append(CRLF);
    writer.append("Content-Disposition: form-data; name=\"binaryFile\"; filename=\"" + binaryFile.getName() + "\"").append(CRLF);
    writer.append("Content-Type: " + URLConnection.guessContentTypeFromName(binaryFile.getName())).append(CRLF);
    writer.append("Content-Transfer-Encoding: binary").append(CRLF);
    writer.append(CRLF).flush();
    Files.copy(binaryFile.toPath(), output);
    output.flush(); // Important before continuing with writer!
    writer.append(CRLF).flush(); // CRLF is important! It indicates end of boundary.

    // End of multipart/form-data.
    writer.append("--" + boundary + "--").append(CRLF).flush();
}

假设服务端还是一个 HttpServlet,它的doPost()方法将会处理这个请求,服务端通过  HttpServletRequest#getPart()获取你发送的内容(注意了,不是getParameter())。getPart()是个比较新的方法,是在Servlet 3.0后才引入的。如果你是Servlet 3.0之前的版本,则可以选用  Apache Commons FileUpload来解析multipart/form-data的请求。可以参考这里的 例子


最后的话
上面啰嗦了很多,Apache提供了工具包,帮助我们更方便地完成这些事情
google也有类似的 工具包


解析、提取HTML内容
如果你是想解析提取html的内容,你可以用 Jsoup等解析器




stackoverflow原址:
http://stackoverflow.com/questions/2793150/using-java-net-urlconnection-to-fire-and-handle-http-requests
   


专栏介绍:
非常喜欢stackoverflow,总能在上面找到疑难杂症的解决办法。偶然发现该网站有一个热度榜单。于是精选了热度较高的一些问题,然后按照自己的理解,把大家的讨论梳理出来。因此,这些文章不是真正的翻译,而是按照自己的理解做了一些增删、润色,希望能把上面的讨论,更精简有效地分享给大家。
如需转载,请注明原文地址

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值