怎样用java.net.URLConnection来触发和处理HTTP请求?

来源:互联网 发布:淘宝评分影响 编辑:程序博客网 时间:2024/05/01 02:32

这是在Stackoverflow的关于 “JAVA HTTP请求” 的经典问答。

将此文翻译并分享下。


问: by BalusC

URLConnection在这里经常被问到,而Oracle关于它的教程太过简单了。那么,我们要怎样用java.net.URLConnection来触发和处理HTTP请求?

有没有关于此的其他一些可能有用的提示和好的范例?


答: by BalusC

首先先发布一个免责声明:发布在此的代码片段都是基础范例。你将需要处理很多琐碎的IOExceptionRuntimeExceptions,如NullPointerExceptionArrayIndexOutOfBoundsException,而且你还要耐得住寂寞。


准备

我们首先至少需要知道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格式,通过&连结。通常你也可以使用URL编码设置查询参数(用URLEncoder#encode()设置指定字符集)。

String#format()只是为了方便才用的。当我需要多次使用字符串连结符+时,我比较喜欢用它。


通过查询参数(可选)触发一个HTTP GET请求

 

这是个不太重要的任务。这是默认的请求方法。

URLConnection connection = new URL(url+ "?" + query).openConnection();connection.setRequestProperty("Accept-Charset",charset);InputStream response = connection.getInputStream();// ...

所有查询字符串都应用?连结到URL。Accept-Charset头文件可能暗示参数编码在服务器中。如果你不发送任何查询字符串,你可以不用去管Accept-Charset头文件。如果你不需要设置任何头文件,你甚至可以使用URL#openStream快捷方法。

InputStream response = newURL(url).openStream();// ...

无论哪种方法,如果另一端是HttpServlet,那么它的doGet()方法将被调用,且参数可被HttpServletRequest#getParameter()使用。


通过查询参数触发一个HTTP POST请求

设置 URLConnection#setDoOutput()true就等于设置请求方法为POST。网页表单要求的标准HTTPPSOT中包含有pplication/x-www-form-urlencoded类型,此类型中查询字符串是针对请求本体编写的。

URLConnection connection = newURL(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表单,不要忘记将name=value<input type="hidden">这两个元素一起放进查询字符串中,当然如果你想要以编程的方式“按”提交按钮的话,也要将name=value<input type="submit">两个元素放在一起(因为通常在服务器端要区分按钮是否被按,如果按了,区分哪一个)。

你也可以将获得的URLConnection计算成HttpURLConnection并用它的HttpURLConnection#setRequestMethod()代替。但是如果你尝试使用这个链接去输出,你还需要将URLConnection#setDoOutput()设置为true

HttpURLConnection httpConnection =(HttpURLConnection) new URL(url).openConnection();httpConnection.setRequestMethod("POST");// ...

无论哪种方法,如果另一边是一个HttpServlet,那么它的doPost()方法将被调用,且HttpServletRequest#getParameter()将可以使用其参数。


实际触发HTTP请求

 

你可以明确的通过URLConnection#connect()来触发HTTP请求,但当你想要得到HTTP响应信息时,请求将自动触发,如响应本体使用URLConnection#getInputStream()等。以上例子完全照做,所以connect()调用事实上是多余的。

收集HTTP响应信息

 

1) HTTP响应状态:

在这里你需要一个HttpURLConnection。需要时优先使用它。

int status =httpConnection.getResponseCode();

2) HTTP响应头文件:

for (Entry<String,List<String>> header : connection.getHeaderFields().entrySet()) {   System.out.println(header.getKey() + "=" + header.getValue());}

3) HTTP响应编码:

Content-Type包含一个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(newInputStreamReader(response, charset))) {       for (String line; (line = reader.readLine()) != null;) {            // ... System.out.println(line) ?       }   }}else {   // It's likely binary content, use InputStream/OutputStream.}

维持会话

 

服务器端会话通常依靠cookie。有些网页表单需要登录和/或会话跟踪。你可以使用CookieHandler接口去维持cookie。你需要在发送所有HTTP请求前通过ACCEPT_ALLCookiePolicy准备一个CookieManager

// First set the default cookiemanager.CookieHandler.setDefault(newCookieManager(null, CookiePolicy.ACCEPT_ALL)); // All the following subsequentURLConnections will use the same cookie manager.URLConnection connection = newURL(url).openConnection();// ... connection = newURL(url).openConnection();// ... connection = newURL(url).openConnection();// ...

注意这并不总能在任何情况正常运行。如果运行失败了,最好的解决办法就是手工收集和设置cookie头文件。你基本上需要从登陆响应或第一个GET请求中获取所有Set-Cookie头文件,然后通过后续的请求将其传递。

// Gather all cookies on the firstrequest.URLConnection connection = newURL(url).openConnection();List<String>cookies =connection.getHeaderFields().get("Set-Cookie");// ... // Then use the same cookies on allsubsequent requests.connection = newURL(url).openConnection();for (String cookie : cookies) {   connection.addRequestProperty("Cookie",cookie.split(";", 2)[0]);}// ...

split(";",2)[0]在这里是为了消除与服务器端无关的cookie属性,如expirespath等。或者,你也可以使用cookie.substring(0,cookie.indexOf(';'))代替split()

流模式

HttpURLConnection默认在实际发送前就将缓冲整个请求本体,无论你是否已经用connection.setRequestProperty("Content-Length",contentLength);设置了一个固定长度。当你同时发送多个较大的POST请求时(如上传文件),可能引起 OutOfMemoryExceptions。为了避免这种情况,你应当设置HttpURLConnection#setFixedLengthStreamingMode()

httpConnection.setFixedLengthStreamingMode(contentLength);

但如果事先不知道发送内容的长度,你可以通过设置HttpURLConnection#setChunkedStreamingMode()使用区块流模式。这将设置HTTPTransfer-Encoding头文件为chunked,并强制请求本体按区块发送。下面这个例子是按每个区块1KB发送的。

httpConnection.setChunkedStreamingMode(1024);

用户代理

当请求返回一个未知的响应,可以在真实的网页浏览器上运行。服务器端可能拒绝了基于User-Agent请求头文件的请求。URLConnection默认将设置他为Java/1.6.0_19,最后这些明显是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.

使用recent brower的User-Agent字符串。

错误处理

 

如果HTTP响应代码是4nn(客户端错误)或5nn(服务器端错误),然后你可能想要读取HttpURLConnection#getErrorStream()看看服务器有没有发送任何有用的错误信息。

InputStream error =((HttpURLConnection) connection).getErrorStream();

如果HTTP响应代码是-1,那么链接和相应处理出现了错误。HttpURLConnection实现在保持连接状态时多多少少的有些bug。可以通过设置http.keepAlive系统为false来把它关掉。而这可以在应用的开始处通过编程来设置:

System.setProperty("http.keepAlive","false");

上传文件

 

一般用multipart/form-data为混合POST内容编码(二进制和字符数据)。更多关于编码的详细资料请参阅RFC2388

String param = "value";File textFile = newFile("/path/to/file.txt");File binaryFile = newFile("/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 = newURL(url).openConnection();connection.setDoOutput(true);connection.setRequestProperty("Content-Type","multipart/form-data; boundary=" + boundary); try (OutputStream output =connection.getOutputStream()) {   try (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 ofboundary.        // 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 ofboundary.        // End of multipart/form-data.       writer.append("--" + boundary + "--").append(CRLF);   }}

如果另一端的是一个HttpServlet,那么它的doPost()方法将被调用,且部分能被HttpServletRequest#getPart()使用(注意,不是getParameter()等)。然而getPart()方法相对来说比较新,在Servlet 3.0(Glassfish 3、Tomcat7等)中才被引入。在Servlet 3.0之前,最好的选择是使用ApacheCommons FileUpload解析multipart/form-data请求。请查看这个回答,其中有FileUpload和Servlet 3.0两种办法的例子。

写在最后

Apache HttpComponents HttpClient比其他的都更方便:)

  • HttpClient教程
  • HttpClient范例

解析和提取HTML

 

如果你想要的是从HTML上解析和提取的数据,那你最好用HTML解析器,如Jsoup。

  • Java中主要的HTML解析器的优/缺点都是什么
  • Java中如何扫描和提取一个网页

 点击原文链接


此文在CC-By-SA 3.0许可证下使用
0 0