gpt4 book ai didi

java - 如何使用 java.net.URLConnection 来触发和处理 HTTP 请求

转载 作者:行者123 更新时间:2023-12-01 16:04:19 26 4
gpt4 key购买 nike

使用 java.net.URLConnection 在这里经常被问到,Oracle tutorial太简洁了。
该教程基本上只展示了如何触发 GET请求并读取响应。它没有在任何地方解释如何使用它来执行 POST。请求、设置请求头、读取响应头、处理 cookie、提交 HTML 表单、上传文件等。
那么,我该如何使用 java.net.URLConnection触发和处理“高级”HTTP 请求?

最佳答案

首先事先声明:发布的代码片段都是基本示例。你需要处理琐碎的IOException s 和 RuntimeException喜欢 NullPointerException , ArrayIndexOutOfBoundsException和你自己。
如果您正在为 Android 而不是 Java 开发,还要注意,自从引入 API 级别 28,明文 HTTP 请求是 disabled by default .我们鼓励您使用 HttpsURLConnection ,但如果真的有必要,可以在应用程序 list 中启用明文。

准备
我们首先至少需要知道 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&param2=%s",
URLEncoder.encode(param1, charset),
URLEncoder.encode(param2, charset));
查询参数必须在 name=value格式并由 & 连接.您通常还会 URL-encode使用 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 header 可能会提示服务器参数的编码方式。如果你不发送任何查询字符串,那么你可以留下 Accept-Charset头球离开。如果您不需要设置任何标题,那么您甚至可以使用 URL#openStream() 快捷方式。
InputStream response = new URL(url).openStream();
// ...
无论哪种方式,如果对方是 HttpServlet ,然后是 doGet() 方法将被调用,参数将由 HttpServletRequest#getParameter() 提供。 .
出于测试目的,您可以将响应正文打印到 standard output如下:
try (Scanner scanner = new Scanner(response)) {
String responseBody = scanner.useDelimiter("\\A").next();
System.out.println(responseBody);
}

发射 HTTP POST带有查询参数的请求
设置 URLConnection#setDoOutput() true将请求方法隐式设置为 POST。 Web 表单所做的标准 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 表单时,请不要忘记使用 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() call其实是多余的。

收集 HTTP 响应信息
  • HTTP response status :

  • 您需要一个 HttpURLConnection 这里。如有必要,请先施放。
        int status = httpConnection.getResponseCode();
  • HTTP response headers :
     for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
    System.out.println(header.getKey() + "=" + header.getValue());
    }
  • HTTP response encoding :

  • 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(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 支持。某些 Web 表单要求您登录和/或被 session 跟踪。您可以使用 CookieHandler 用于维护 cookie 的 API。您需要准备一个 CookieManager CookiePolicy ACCEPT_ALL 在发送所有 HTTP 请求之前。
    // 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 header 。你基本上需要抢所有 Set-Cookie来自登录响应或第一个 GET 的 header 请求,然后通过后续请求传递它。
    // 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 属性,例如 expires , path等。或者,您也可以使用 cookie.substring(0, cookie.indexOf(';'))而不是 split() .

    流媒体模式
    HttpURLConnection 默认情况下,将在实际发送之前缓冲整个请求正文,无论您是否使用 connection.setRequestProperty("Content-Length", contentLength); 自己设置了固定的内容长度| .这可能会导致 OutOfMemoryException s 每当您同时发送大型 POST 请求(例如上传文件)。为避免这种情况,您需要设置 HttpURLConnection#setFixedLengthStreamingMode() .
    httpConnection.setFixedLengthStreamingMode(contentLength);
    但是如果内容长度真的事先不知道,那么你可以通过设置 HttpURLConnection#setChunkedStreamingMode() 来使用分块流模式。因此。这将设置 HTTP Transfer-Encoding 标题到 chunked这将强制以块的形式发送请求正文。下面的示例将以 1 KB 的块发送正文。
    httpConnection.setChunkedStreamingMode(1024);

    用户代理
    可能会发生 a request returns an unexpected response, while it works fine with a real web browser .服务器端可能基于 User-Agent 阻塞了请求请求头。 URLConnection默认情况下将其设置为 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.
    使用来自 recent browser 的 User-Agent 字符串.

    错误处理
    如果 HTTP 响应代码是 4nn (客户端错误)或 5nn (服务器错误),那么您可能需要阅读 HttpURLConnection#getErrorStream()查看服务器是否发送了任何有用的错误信息。
    InputStream error = ((HttpURLConnection) connection).getErrorStream();
    如果 HTTP 响应代码为 -1,则连接和响应处理出现问题。 HttpURLConnection实现在较旧的 JRE 中在保持连接 Activity 方面有些问题。您可能希望通过设置 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();
    }
    如果对方是 HttpServlet ,然后是 doPost() 方法将被调用,部件将由 HttpServletRequest#getPart() 提供。 (注意,因此 不是 getParameter() 等等!)。 getPart()然而,方法相对较新,它是在 Servlet 3.0(Glassfish 3、Tomcat 7 等)中引入的。在 Servlet 3.0 之前,最好的选择是使用 Apache Commons FileUpload解析 multipart/form-data要求。另见 this answer有关 FileUpload 和 Servelt 3.0 方法的示例。

    处理不受信任或配置错误的 HTTPS 站点
    如果您正在为 Android 而不是 Java 开发, 小心 :如果您在开发过程中没有部署正确的证书,下面的解决方法可能会节省您的时间。但是您不应该将它用于生产。这些天(2021 年 4 月)如果 Google 检测到不安全的主机名验证程序,他们将不允许您的应用在 Play 商店中分发,请参阅 https://support.google.com/faqs/answer/7188426.
    有时您需要连接 HTTPS URL,可能是因为您正在编写网络爬虫。在这种情况下,您可能会遇到 javax.net.ssl.SSLException: Not trusted server certificate在某些未更新 SSL 证书的 HTTPS 站点上,或 java.security.cert.CertificateException: No subject alternative DNS name matching [hostname] foundjavax.net.ssl.SSLProtocolException: handshake alert: unrecognized_name在一些配置错误的 HTTPS 站点上。
    以下一次性运行 static您的网络爬虫类中的初始化程序应该使 HttpsURLConnection对那些 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 中解析和提取数据,那么最好使用像 Jsoup 这样的 HTML 解析器。 .
  • What are the pros/cons of leading HTML parsers in Java
  • How to scan and extract a webpage in Java
  • 关于java - 如何使用 java.net.URLConnection 来触发和处理 HTTP 请求,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2793150/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com