好问题
Good  Question
  • 首 页
  • 问题
    • PHP
    • JAVA
    • CPlusPlus
    • C#
    • SQL
  • 关 于
  • 联 系
使用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请求?

回答:

首先至少要了解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 (Entry> header : connection.getHeaderFields().entrySet()) {
    System.out.println(header.getKey() + "=" + header.getValue());}
3.Http 响应编码
当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();List 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属性,类似expres,path等。另外,你可以使用cookie.substring(0, cookie.indexOf(';')) 而不是 split().
流模式
不管是否你已经使用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。
上一篇怎么把浮点数转化为字符串并去掉不必要的0?
在JAVA中如何将一个字符串保存为文本?下一篇
下一篇在JAVA中如何将一个字符串保存为文本?

最新文章

  • 函数`__construct`用来干嘛的
    发布日期:2018-03-26
  • 通过访客的IP得到他们的地区
    发布日期:2018-03-26
  • 合并两个PHP对象的最好的方法是什么?
    发布日期:2018-03-26
  • 该如何把一该如何把一个对象转化成数组?
    发布日期:2018-03-26
  • 什么是输出缓冲区?
    发布日期:2018-03-26
  • 在PHP中怎么把用逗号分隔的字符串分隔在一个数组里?
    发布日期:2018-03-26
  • 在PHP中使用foreach循环时查找数组的最后一个元素
    发布日期:2018-03-26
关于好问
收集整理一些有用的问题和回答,造福中国的程序旺和IT喵们!
友情链接
起飞页 
相关信息
版权声明
Copyright © 2016 - 2022  苏州卡达网络科技有限公司 备案号:苏ICP备09008221号