【问题标题】:How to use java.net.URLConnection to fire and handle HTTP requests如何使用 java.net.URLConnection 触发和处理 HTTP 请求
【发布时间】:2011-02-17 02:24:33
【问题描述】:

java.net.URLConnection 的使用在这里经常被问及,Oracle tutorial简洁了。

该教程基本上只展示了如何触发GET 请求并读取响应。它没有说明如何使用它来执行POST 请求、设置请求标头、读取响应标头、处理 cookie、提交 HTML 表单、上传文件等。

那么,我如何使用java.net.URLConnection 来触发和处理“高级”HTTP 请求?

【问题讨论】:

  • 一些有很多投票和一般标题的问题被标记为重复并链接到这个问题。此外,还有new ways 可以在 Java 中执行 HTTP 请求,并且将来可能会弃用旧的 URLConnection 方式。这个问题的题目应该改成How to execute HTTP requests in Java

标签: java http httprequest httpurlconnection urlconnection


【解决方案1】:

先声明一下:贴出的代码sn-ps都是基本的例子。您需要处理琐碎的IOExceptions 和RuntimeExceptions,例如NullPointerExceptionArrayIndexOutOfBoundsException 和自己的配偶。

如果您正在为 Android 而不是 Java 进行开发,还请注意,自从引入 API 级别 28 以来,明文 HTTP 请求为 disabled by default。鼓励您使用HttpsURLConnection,但如果确实需要,可以在应用程序清单中启用明文。


准备中

我们首先需要至少知道 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 格式并由& 连接。您通常还可以使用URLEncoder#encode() 指定字符集的查询参数URL-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 标头放在一边。如果你不需要设置任何headers,那么你甚至可以使用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 表单时,不要忘记将任何 <input type="hidden"> 元素的 name=value 对放入查询字符串,当然还有 name=value 对987654400@ 您想以编程方式“按下”的元素(因为它通常在服务器端用于区分是否按下了按钮,如果按下了,则区分哪个按钮)。

您也可以将获得的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 response status:

您需要在这里HttpURLConnection。必要时先施放。

    int status = httpConnection.getResponseCode();
  1. HTTP response headers:

     for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
         System.out.println(header.getKey() + "=" + header.getValue());
     }
    
  2. 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.
    }

维护会话

服务器端会话通常由 cookie 支持。某些 Web 表单要求您登录和/或由会话跟踪。您可以使用CookieHandler API 来维护 cookie。在发送所有 HTTP 请求之前,您需要准备 CookieManagerCookiePolicyACCEPT_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 标头,然后将其传递给后续请求。

// 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 属性,例如 expirespath 等。或者,您也可以使用 cookie.substring(0, cookie.indexOf(';')) 而不是 split()


串流模式

HttpURLConnection 默认情况下会在实际发送之前缓冲整个 请求正文,无论您是否使用connection.setRequestProperty("Content-Length", contentLength); 自己设置了固定内容长度。每当您同时发送大型 POST 请求(例如上传文件)时,这可能会导致 OutOfMemoryExceptions。为避免这种情况,您需要设置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 的用户代理字符串。


错误处理

如果 HTTP 响应代码是 4nn(客户端错误)或 5nn(服务器错误),那么您可能需要阅读 HttpURLConnection#getErrorStream() 以查看服务器是否发送了任何有用的错误信息。

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

如果 HTTP 响应代码为 -1,则连接和响应处理出现问题。 HttpURLConnection 实现在较旧的 JRE 中在保持连接活动方面有些错误。您可能希望通过将http.keepAlive 系统属性设置为false 来关闭它。您可以通过以下方式在应用程序的开头以编程方式执行此操作:

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

上传文件

对于混合的 POST 内容(二进制和字符数据),您通常会使用 multipart/form-data 编码。编码在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 请求。有关 FileUpload 和 Servelt 3.0 方法的示例,另请参阅 this answer


处理不受信任或配置错误的 HTTPS 站点

如果您是为 Android 而不是 Java 开发,小心:如果您在开发期间没有部署正确的证书,下面的解决方法可能会节省您的时间。但是您不应该将其用于生产。这些天(2021 年 4 月)如果 Google 检测到不安全的主机名验证程序,他们将不允许您的应用在 Play 商店中分发,请参阅 https://support.google.com/faqs/answer/7188426.

有时您需要连接 HTTPS URL,可能是因为您正在编写网络爬虫。在这种情况下,您可能会在某些 HTTPS 站点上遇到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

您的网络爬虫类中的以下一次性运行 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 在这方面要方便得多 :)


解析和提取 HTML

如果您只想从 HTML 中解析和提取数据,那么最好使用像 Jsoup 这样的 HTML 解析器。

【讨论】:

  • @imperator:这是服务器代码中的错误。它抛出了一个异常。 getErrorStream() 中提供了可能包含详细异常信息的错误页面。另请参阅上述答案中的“错误处理”部分。或者,如果它是您自己的服务器,请阅读其服务器日志。
  • @Brais:请阅读规范。 -- 部分不是边界本身的一部分。它只是一个分隔符字符串。我已回滚您的无效编辑。
  • 可悲的是,在 Android 上建议现在使用 Apache HttpClientHttpURLConnection 是残酷的。 android-developers.blogspot.in/2011/09/…
  • 根据我的简单测量 URLConn 比 Apache.Httpclient 快 400-500ms
  • @Haresh:它们是 Java 7 的一部分。就像那些 try-with-resources 语句一样。也许您仍在使用长期 EOL 的 Java 6 或更早版本?只需至少升级到 Java 7(我们目前已经在使用 Java 8),或者用较旧的 Java 等效代码替换 Java 7 代码。例如。一个老式的 for 循环将缓冲区从输入写入输出。
【解决方案2】:

在使用 HTTP 时,引用 HttpURLConnection 而不是基类 URLConnection 几乎总是更有用(因为 URLConnection 是一个抽象类,当您在 HTTP URL 上请求 URLConnection.openConnection() 时,这就是您的目标。无论如何都会回来的)。

然后您可以代替依赖URLConnection#setDoOutput(true) 将请求方法隐式设置为POST,而是使用httpURLConnection.setRequestMethod("POST"),这可能会让某些人觉得更自然(并且还允许您指定其他请求方法例如 PUTDELETE、...)。

它还提供了有用的 HTTP 常量,因此您可以:

int responseCode = httpURLConnection.getResponseCode();

if (responseCode == HttpURLConnection.HTTP_OK) {

【讨论】:

  • setDoOutPut true 是我的问题,它将我的 GET 设置为 POST。谢谢
  • 如果您尝试将数据写入输出流,您必须仍将setDoOutput() 设置为true,否则将引发异常(即使您setRequestMethod("POST"))。需要明确的是:将URLConnection#setDoOutput(true) 设置为true 隐式将请求方法设置为POST,但将httpURLConnection.setRequestMethod("POST") 设置为POST 确实setDoOutput() 隐式设置为true
  • 8 年后,setRequestMethod("POST")确实doOutput 设置为true
【解决方案3】:

受 StackOverflow 上的这个问题和其他问题的启发,我创建了一个最小的开源 basic-http-client,它体现了这里发现的大部分技术。

google-http-java-client 也是一个很棒的开源资源。

【讨论】:

  • 我也是这么想的。但是,如果有一个准系统/简单的 Java 库,它只使用此处介绍的 URLConnection 代码,将代码封装为更简单的方法来执行 HTTP GET、POST 等,也可能会很好。然后可以将库编译和打包为 JAR 和如果不需要外部 JAR,在 Java 代码中导入/使用或源类文件可以包含在 Java 项目中。这可以通过 Apache 等其他库来完成,但与使用 URLConnection 的简单 1 文件类库相比更加痛苦。
  • rapidvaluesolutions.com/tech_blog/… 支持 HttpURLConnection 而不是 HttpClient
【解决方案4】:

我建议你看看kevinsawicki/http-request 上的代码,它基本上是HttpUrlConnection 之上的一个包装器,它提供了一个更简单的 API,以防你现在只想发出请求,或者你可以看看在源头(它不是太大)来看看如何处理连接。

示例:使用内容类型application/json 和一些查询参数发出GET 请求:

// GET http://google.com?q=baseball%20gloves&size=100
String response = HttpRequest.get("http://google.com", true, "q", "baseball gloves", "size", 100)
        .accept("application/json")
        .body();
System.out.println("Response was: " + response);

【讨论】:

    【解决方案5】:

    更新

    新的 HTTP 客户端随 Java 9 一起提供,但作为 孵化器模块名为jdk.incubator.httpclient。孵化器模块是 一种将非最终 API 交到开发人员手中的方法,而 API 在未来朝着最终确定或删除的方向发展 释放。

    在 Java 9 中,您可以发送 GET 请求,例如:

    // GET
    HttpResponse response = HttpRequest
        .create(new URI("http://www.stackoverflow.com"))
        .headers("Foo", "foovalue", "Bar", "barvalue")
        .GET()
        .response();
    

    然后你可以检查返回的HttpResponse

    int statusCode = response.statusCode();
    String responseBody = response.body(HttpResponse.asString());
    

    由于这个新的 HTTP 客户端在 java.httpclient jdk.incubator.httpclient 模块中,你应该在你的 module-info.java 文件中声明这个依赖:

    module com.foo.bar {
        requires jdk.incubator.httpclient;
    }
    

    【讨论】:

    • 进一步更新:模块处于孵化状态。现在是 java.net.http,而不是 jdk.incubator.httpclient。
    【解决方案6】:

    HTTP URL Hits 有两个选项:GET / POST

    GET 请求:

    HttpURLConnection.setFollowRedirects(true); // Defaults to true
    
    String url = "https://name_of_the_url";
    URL request_url = new URL(url);
    HttpURLConnection http_conn = (HttpURLConnection)request_url.openConnection();
    http_conn.setConnectTimeout(100000);
    http_conn.setReadTimeout(100000);
    http_conn.setInstanceFollowRedirects(true);
    System.out.println(String.valueOf(http_conn.getResponseCode()));
    

    POST 请求:

    HttpURLConnection.setFollowRedirects(true); // Defaults to true
    
    String url = "https://name_of_the_url"
    URL request_url = new URL(url);
    HttpURLConnection http_conn = (HttpURLConnection)request_url.openConnection();
    http_conn.setConnectTimeout(100000);
    http_conn.setReadTimeout(100000);
    http_conn.setInstanceFollowRedirects(true);
    http_conn.setDoOutput(true);
    PrintWriter out = new PrintWriter(http_conn.getOutputStream());
    if (urlparameter != null) {
       out.println(urlparameter);
    }
    out.close();
    out = null;
    System.out.println(String.valueOf(http_conn.getResponseCode()));
    

    【讨论】:

    • 如何查看实际的 JSON 响应?
    【解决方案7】:

    这个回复也让我深受启发。

    我经常在需要做一些 HTTP 的项目上,我可能不想引入很多第三方依赖项(这些依赖项会引入其他等等等等)

    我开始根据其中的一些对话编写自己的实用程序(不是任何地方):

    package org.boon.utils;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.net.URLConnection;
    import java.util.Map;
    
    import static org.boon.utils.IO.read;
    
    public class HTTP {
    

    那么只有一堆或静态方法。

    public static String get(
            final String url) {
    
        Exceptions.tryIt(() -> {
            URLConnection connection;
            connection = doGet(url, null, null, null);
            return extractResponseString(connection);
        });
        return null;
    }
    
    public static String getWithHeaders(
            final String url,
            final Map<String, ? extends Object> headers) {
        URLConnection connection;
        try {
            connection = doGet(url, headers, null, null);
            return extractResponseString(connection);
        } catch (Exception ex) {
            Exceptions.handle(ex);
            return null;
        }
    }
    
    public static String getWithContentType(
            final String url,
            final Map<String, ? extends Object> headers,
            String contentType) {
        URLConnection connection;
        try {
            connection = doGet(url, headers, contentType, null);
            return extractResponseString(connection);
        } catch (Exception ex) {
            Exceptions.handle(ex);
            return null;
        }
    }
    public static String getWithCharSet(
            final String url,
            final Map<String, ? extends Object> headers,
            String contentType,
            String charSet) {
        URLConnection connection;
        try {
            connection = doGet(url, headers, contentType, charSet);
            return extractResponseString(connection);
        } catch (Exception ex) {
            Exceptions.handle(ex);
            return null;
        }
    }
    

    那就发帖吧……

    public static String postBody(
            final String url,
            final String body) {
        URLConnection connection;
        try {
            connection = doPost(url, null, "text/plain", null, body);
            return extractResponseString(connection);
        } catch (Exception ex) {
            Exceptions.handle(ex);
            return null;
        }
    }
    
    public static String postBodyWithHeaders(
            final String url,
            final Map<String, ? extends Object> headers,
            final String body) {
        URLConnection connection;
        try {
            connection = doPost(url, headers, "text/plain", null, body);
            return extractResponseString(connection);
        } catch (Exception ex) {
            Exceptions.handle(ex);
            return null;
        }
    }
    
    
    public static String postBodyWithContentType(
            final String url,
            final Map<String, ? extends Object> headers,
            final String contentType,
            final String body) {
    
        URLConnection connection;
        try {
            connection = doPost(url, headers, contentType, null, body);
    
            return extractResponseString(connection);
    
        } catch (Exception ex) {
            Exceptions.handle(ex);
            return null;
        }
    
    }
    
    
    public static String postBodyWithCharset(
            final String url,
            final Map<String, ? extends Object> headers,
            final String contentType,
            final String charSet,
            final String body) {
    
        URLConnection connection;
        try {
            connection = doPost(url, headers, contentType, charSet, body);
    
            return extractResponseString(connection);
    
        } catch (Exception ex) {
            Exceptions.handle(ex);
            return null;
        }
    }
    
    private static URLConnection doPost(String url, Map<String, ? extends Object> headers,
                                        String contentType, String charset, String body
                                        ) throws IOException {
        URLConnection connection;/* Handle output. */
        connection = new URL(url).openConnection();
        connection.setDoOutput(true);
        manageContentTypeHeaders(contentType, charset, connection);
    
        manageHeaders(headers, connection);
    
        IO.write(connection.getOutputStream(), body, IO.CHARSET);
        return connection;
    }
    
    private static void manageHeaders(Map<String, ? extends Object> headers, URLConnection connection) {
        if (headers != null) {
            for (Map.Entry<String, ? extends Object> entry : headers.entrySet()) {
                connection.setRequestProperty(entry.getKey(), entry.getValue().toString());
            }
        }
    }
    
    private static void manageContentTypeHeaders(String contentType, String charset, URLConnection connection) {
        connection.setRequestProperty("Accept-Charset", charset == null ? IO.CHARSET : charset);
        if (contentType!=null && !contentType.isEmpty()) {
            connection.setRequestProperty("Content-Type", contentType);
        }
    }
    
    private static URLConnection doGet(String url, Map<String, ? extends Object> headers,
                                        String contentType, String charset) throws IOException {
        URLConnection connection;/* Handle output. */
        connection = new URL(url).openConnection();
        manageContentTypeHeaders(contentType, charset, connection);
    
        manageHeaders(headers, connection);
    
        return connection;
    }
    
    private static String extractResponseString(URLConnection connection) throws IOException {
    /* Handle input. */
        HttpURLConnection http = (HttpURLConnection)connection;
        int status = http.getResponseCode();
        String charset = getCharset(connection.getHeaderField("Content-Type"));
    
        if (status==200) {
            return readResponseBody(http, charset);
        } else {
            return readErrorResponseBody(http, status, charset);
        }
    }
    
    private static String readErrorResponseBody(HttpURLConnection http, int status, String charset) {
        InputStream errorStream = http.getErrorStream();
        if ( errorStream!=null ) {
            String error = charset== null ? read( errorStream ) :
                read( errorStream, charset );
            throw new RuntimeException("STATUS CODE =" + status + "\n\n" + error);
        } else {
            throw new RuntimeException("STATUS CODE =" + status);
        }
    }
    
    private static String readResponseBody(HttpURLConnection http, String charset) throws IOException {
        if (charset != null) {
            return read(http.getInputStream(), charset);
        } else {
            return read(http.getInputStream());
        }
    }
    
    private static String getCharset(String contentType) {
        if (contentType==null)  {
            return null;
        }
        String charset = null;
        for (String param : contentType.replace(" ", "").split(";")) {
            if (param.startsWith("charset=")) {
                charset = param.split("=", 2)[1];
                break;
            }
        }
        charset = charset == null ? IO.CHARSET : charset;
    
        return charset;
    }
    

    嗯,你明白了....

    这里是测试:

    static class MyHandler implements HttpHandler {
        public void handle(HttpExchange t) throws IOException {
    
            InputStream requestBody = t.getRequestBody();
            String body = IO.read(requestBody);
            Headers requestHeaders = t.getRequestHeaders();
            body = body + "\n" + copy(requestHeaders).toString();
            t.sendResponseHeaders(200, body.length());
            OutputStream os = t.getResponseBody();
            os.write(body.getBytes());
            os.close();
        }
    }
    
    
    @Test
    public void testHappy() throws Exception {
    
        HttpServer server = HttpServer.create(new InetSocketAddress(9212), 0);
        server.createContext("/test", new MyHandler());
        server.setExecutor(null); // creates a default executor
        server.start();
    
        Thread.sleep(10);
    
        Map<String,String> headers = map("foo", "bar", "fun", "sun");
    
        String response = HTTP.postBodyWithContentType("http://localhost:9212/test", headers, "text/plain", "hi mom");
    
        System.out.println(response);
    
        assertTrue(response.contains("hi mom"));
        assertTrue(response.contains("Fun=[sun], Foo=[bar]"));
    
        response = HTTP.postBodyWithCharset("http://localhost:9212/test", headers, "text/plain", "UTF-8", "hi mom");
    
        System.out.println(response);
    
        assertTrue(response.contains("hi mom"));
        assertTrue(response.contains("Fun=[sun], Foo=[bar]"));
    
        response = HTTP.postBodyWithHeaders("http://localhost:9212/test", headers, "hi mom");
    
        System.out.println(response);
    
        assertTrue(response.contains("hi mom"));
        assertTrue(response.contains("Fun=[sun], Foo=[bar]"));
    
        response = HTTP.get("http://localhost:9212/test");
    
        System.out.println(response);
    
        response = HTTP.getWithHeaders("http://localhost:9212/test", headers);
    
        System.out.println(response);
    
        assertTrue(response.contains("Fun=[sun], Foo=[bar]"));
    
        response = HTTP.getWithContentType("http://localhost:9212/test", headers, "text/plain");
    
        System.out.println(response);
    
        assertTrue(response.contains("Fun=[sun], Foo=[bar]"));
    
        response = HTTP.getWithCharSet("http://localhost:9212/test", headers, "text/plain", "UTF-8");
    
        System.out.println(response);
    
        assertTrue(response.contains("Fun=[sun], Foo=[bar]"));
    
        Thread.sleep(10);
    
        server.stop(0);
    }
    
    @Test
    public void testPostBody() throws Exception {
    
        HttpServer server = HttpServer.create(new InetSocketAddress(9220), 0);
        server.createContext("/test", new MyHandler());
        server.setExecutor(null); // creates a default executor
        server.start();
    
        Thread.sleep(10);
    
        Map<String,String> headers = map("foo", "bar", "fun", "sun");
    
        String response = HTTP.postBody("http://localhost:9220/test", "hi mom");
    
        assertTrue(response.contains("hi mom"));
    
        Thread.sleep(10);
    
        server.stop(0);
    }
    
    @Test(expected = RuntimeException.class)
    public void testSad() throws Exception {
    
        HttpServer server = HttpServer.create(new InetSocketAddress(9213), 0);
        server.createContext("/test", new MyHandler());
        server.setExecutor(null); // creates a default executor
        server.start();
    
        Thread.sleep(10);
    
        Map<String,String> headers = map("foo", "bar", "fun", "sun");
    
        String response = HTTP.postBodyWithContentType("http://localhost:9213/foo", headers, "text/plain", "hi mom");
    
        System.out.println(response);
    
        assertTrue(response.contains("hi mom"));
        assertTrue(response.contains("Fun=[sun], Foo=[bar]"));
    
        Thread.sleep(10);
    
        server.stop(0);
    }
    

    你可以在这里找到其余的:

    https://github.com/RichardHightower/boon

    我的目标是以更简单的方式提供人们想要做的常见事情......

    【讨论】:

    • 奇怪的是,doPost 方法中有一个charset 参数,用于设置请求标头,但随后数据是用一些硬编码的字符集IO.CHARSET 写入的。一个错误?
    【解决方案8】:

    一开始我被this article 误导了HttpClient

    后来我意识到HttpURLConnection 将离开this article

    根据 Google 博客

    Apache HTTP 客户端在 Eclair 和 Froyo 上的错误更少。这是这些版本的最佳选择。对于 Gingerbread , HttpURLConnection 是最佳选择。其简单的 API 和小尺寸使其非常适合 Android。

    透明压缩和响应缓存可减少网络使用、提高速度并节省电池。新应用程序应该使用 HttpURLConnection;这是我们将在未来花费精力的地方。

    在阅读了this article 和其他一些堆栈溢出问题后,我确信HttpURLConnection 会持续更长时间。

    一些支持HttpURLConnections的SE问题:

    On Android, make a POST request with URL Encoded Form data without using UrlEncodedFormEntity

    HttpPost works in Java project, but not on Android

    【讨论】:

      【解决方案9】:

      还有OkHttp,这是一个默认高效的HTTP客户端:

      • HTTP/2 支持允许对同一主机的所有请求共享一个套接字。
      • 连接池可减少请求延迟(如果 HTTP/2 不可用)。
      • 透明 GZIP 可缩小下载大小。
      • 响应缓存完全避免了网络重复请求。

      首先创建OkHttpClient的实例:

      OkHttpClient client = new OkHttpClient();
      

      然后,准备您的GET 请求:

      Request request = new Request.Builder()
            .url(url)
            .build();
      

      最后,使用OkHttpClient发送准备好的Request

      Response response = client.newCall(request).execute();
      

      更多详情可以咨询OkHttp's documentation

      【讨论】:

        【解决方案10】:

        您还可以使用jcabi-http 中的JdkRequest(我是一名开发人员),它会为您完成所有这些工作,装饰 HttpURLConnection、触发 HTTP 请求和解析响应,例如:

        String html = new JdkRequest("http://www.google.com").fetch().body();
        

        查看这篇博文了解更多信息:http://www.yegor256.com/2014/04/11/jcabi-http-intro.html

        【讨论】:

        • 你们如何处理cookies?
        【解决方案11】:

        如果您使用的是 HTTP GET,请删除此行:

        urlConnection.setDoOutput(true);
        

        【讨论】:

          【解决方案12】:

          如果您使用的是 Java 11 (except on Android),而不是旧的 HttpUrlConnection 类,您可以使用 Java 11 新的 HTTP Client API

          GET 请求示例:

          var uri = URI.create("https://httpbin.org/get?age=26&isHappy=true");
          var client = HttpClient.newHttpClient();
          var request = HttpRequest
                  .newBuilder()
                  .uri(uri)
                  .header("accept", "application/json")
                  .GET()
                  .build();
          var response = client.send(request, HttpResponse.BodyHandlers.ofString());
          System.out.println(response.statusCode());
          System.out.println(response.body());
          

          同一个请求异步执行:

          var responseAsync = client
                  .sendAsync(request, HttpResponse.BodyHandlers.ofString())
                  .thenApply(HttpResponse::body)
                  .thenAccept(System.out::println);
          // responseAsync.join(); // Wait for completion
          

          POST 请求示例:

          var request = HttpRequest
                  .newBuilder()
                  .uri(uri)
                  .version(HttpClient.Version.HTTP_2)
                  .timeout(Duration.ofMinutes(1))
                  .header("Content-Type", "application/json")
                  .header("Authorization", "Bearer fake")
                  .POST(BodyPublishers.ofString("{ title: 'This is cool' }"))
                  .build();
          var response = client.send(request, HttpResponse.BodyHandlers.ofString());
          

          要以多部分 (multipart/form-data) 或 url 编码 (application/x-www-form-urlencoded) 格式发送表单数据,请参阅this solution

          有关 HTTP 客户端 API 的示例和更多信息,请参阅 this article

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多