发布日期:2018-03-26
使用java.net.URLConnection触发和处理HTTP请求(URLConnection 是根据 URL 创建的,是用于访问 URL 所指向资源的通信链接。此抽象类将大多数工作委托给底层协议处理程序,如 http 或 ftp。 )+ 查看更多
使用java.net.URLConnection触发和处理HTTP请求(URLConnection 是根据 URL 创建的,是用于访问 URL 所指向资源的通信链接。此抽象类将大多数工作委托给底层协议处理程序,如 http 或 ftp。 )
+ 查看更多
发布日期:2018-03-26 10:31
分类:JAVA
浏览次数:102
如下:
使用java.net.URLConnection是很常见的,并且Oracle(甲骨文公司)教程也很简洁。
教程只是展示了基础的部分,类似触发一个GET请求,得到回应。并没有解释如何使用其他形式发送一个POST请求,设置请求头部,阅读请求头部,处理缓存,提交HTML表单,上传文件等
所以,如何使用java.net.URLConnection触发和处理更高级的HTTP请求?
教程只是展示了基础的部分,类似触发一个GET请求,得到回应。并没有解释如何使用其他形式发送一个POST请求,设置请求头部,阅读请求头部,处理缓存,提交HTML表单,上传文件等
所以,如何使用java.net.URLConnection触发和处理更高级的HTTP请求?
回答:
首先至少要了解URL和字符集。设置的参数是可以选择的,取决于函数功能。
String url = "http://example.com";String charset = "UTF-8"; // Or in Java 7 and later, use the constant: java.nio.charset.StandardCharsets.UTF_8.name()String param1 = "value1";String param2 = "value2";// ... String query = String.format("param1=%s¶m2=%s", URLEncoder.encode(param1, charset), URLEncoder.encode(param2, charset));查询参数必须是键值对的形式,而且以&符号连接在一起。通常可以使用特殊的使用URLEncode#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 = new URL.openStream();// ...
可通过
而且,如果接收方是HttpServlet,那么它的doGet()方法会被调用,参数可通过HttpServletRequest # getparameter()获得。
测试一下,你可以像下面这样将回应输出:
try (Scanner scanner = new Scanner(response)) { String responseBody = scanner.useDelimiter("\\A").next(); System.out.println(responseBody);}利用查询参数触发HTTP POST请求:
将URLConnection#setDoOutput()设置为true可以将请求方式改为POST.
标准的HTTP POST 网页形式是application/x-www-form-urlencoded的类型的,查询字符串将写入其请求体中。
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">元素的键值对改为查询字符串,当然你喜欢编程去点击的<input type="submit">元素的键值对也是如此(因为这经常被服务器使用去区分是否一个按钮被按下,哪个按钮被按下)。
你也可以将获取的URLConnection转换为HttpConnection,并且使用HttpURLConnection#setRequestMethod() 作为替代。但是如果你为了输出使用这个连接,仍然需要将URLConnection#setDoOutput()设置为true。
HttpURLConnection httpConnection = (HttpURLConnection) new URL(url).openConnection(); httpConnection.setRequestMethod("POST");// ...触发HTTP请求
你可以使用 URLConnection#connect()触发HTTP请求,但是当你想得到HTTP回应的任何信息,请求将会按照需求自动的触发。类似于使用URLConnection#getInputStream() 来接受HTTP回应体等。上面的例子就是这样做的,所以事实上connect()调用是多余的
收集HTTP响应信息
1.HTTP响应状态:
需要HttpURLConnection().如果需要的话先将其转换。
int status = httpConnection.getResponseCode();2.Http 响应头部
for (Entry3.Http 响应编码> header : connection.getHeaderFields().entrySet()) { System.out.println(header.getKey() + "=" + header.getValue());}
当Content-Type包含字符集元素,响应体很可能是基于文本的,我们希望使用服务器端指定的编码来处理它。
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.}维持会话
服务器端会话通常由cookie支持。服务器端会话通常由cookie支持。某些web表单要求您登录和/或会话跟踪。你可以使用cookiehandler API保持cookies。
在发送所有的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头部。你需要掌握所有登录或第一次GET请求的Set-Cookie头部信息,然后通过随后的请求。
URLConnection connection = new URL(url).openConnection();Listsplit(";", 2)[0]是为了避免与服务器放无关的cookie属性,类似expres,path等。另外,你可以使用cookie.substring(0, cookie.indexOf(';')) 而不是 split().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]);}// ...
流模式
不管是否你已经使用connection.setRequestProperty("Content-Length", contentLength)设置了一个固定的长度,HttpConnection默认在发送之前缓冲所有请求体;当你同时发送大量的POST请求时,可能会导致OutOfMemoryException(例如,上传文件)。为了避免这点,你可以设置
HttpURLConnection#setFixedLengthStreamingMode(),像下面这样:
httpConnection.setFixedLengthStreamingMode(contentLength);但是如果内容长度事先不知道,那么你可以通过设置HttpURLConnection#setChunkedStreamingMode()利用将流分块的形式。设置HTTPTransfer-Encoding头部将会强制按照块发送请求体。下面的例子发送块长度为1KB:
httpConnection.setChunkedStreamingMode(1024);用户代理
请求可能会返回一个意料之外的响应,,尽管它在真是的网页浏览器上运行的很好。服务器方可能阻塞基于用户代理请求头的请求。URLConnection默认被设置为java/1.6.0_19,java/1.6.0_19最后一部分显示的是JRE版本。你可以覆盖它:
connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"); // Do as if you're using Chrome 41 on Windows 7.错误处理
如果HTTP响应码是4nn(用户错误)或者5nn(服务器错误),你可能会想读HttpURLConnection#getErrorStream()去检查是否服务器发送了任何有用的错误信息。
InputStream error = ((HttpURLConnection) connection).getErrorStream();如果HTTP响应码是-1,一定是哪里连接和响应处理哪里出了错误。HttpURLConnection在旧的JRE版本上实施并且保持连接。你可能想关闭它通过设置http.keepAlive 系统属性为false.你可以在你的应用的开头这样编写:
System.setProperty("http.keepAlive", "false");上传文件:
通常使用multipart/form-data混合编码POST(二进制和字符数据)。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();}处理不可信或则设置错误的HTTPS站点
有时候你需要连接HTTPS URL,或许因为你在写网页scraper(Google Chrome 扩展,用于从网页获取数据并存到电子表格)。这种情况下你可能会面临javax.net.ssl.SSLException: Not trusted server certificate在一些到目前为止没有保持SSL证书的网站,或者java.security.cert.CertificateException: No subject alternative DNS name matching [hostname] found 或 javax.net.ssl.SSLProtocolException: handshake alert: unrecognized_name 在一些配置错误的HTTPS站点。
,
下面的只运行一次的static 在你的网页scraper上初始化比那些HTTPS站点更为容易,并且不会抛出任何异常。
static { TrustManager[] trustAllCertificates = new TrustManager[] { new X509TrustManager() { @Override public X509Certificate[] getAcceptedIssuers() { return null; // Not relevant. } @Override public void checkClientTrusted(X509Certificate[] certs, String authType) { // Do nothing. Just allow them all. } @Override public void checkServerTrusted(X509Certificate[] certs, String authType) { // Do nothing. Just allow them all. } } }; HostnameVerifier trustAllHostnames = new HostnameVerifier() { @Override public boolean verify(String hostname, SSLSession session) { return true; // Just allow them all. } }; try { System.setProperty("jsse.enableSNIExtension", "false"); SSLContext sc = SSLContext.getInstance("SSL"); sc.init(null, trustAllCertificates, new SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); HttpsURLConnection.setDefaultHostnameVerifier(trustAllHostnames); } catch (GeneralSecurityException e) { throw new ExceptionInInitializerError(e); } }最后,
Apache HttpComponents HttpClient 更方便。
HttpClient Tutorial
HttpClient Examples
解析和提取HTML:
如果你想从HTML中解析和提取数据,使用HTML解析器 Jsoup
What are the pros/cons of leading HTML parsers in Java
How to scan and extract a webpage in Java。