如何使用java.net.URLConnection发送和处理HTTP请求

2128

在这里,经常会被询问如何使用java.net.URLConnection,而Oracle教程对此过于简略。

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

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


2
一些投票数很高且标题通用的问题被标记为重复,并链接到此问题。此外,Java 中有新的方法来执行 HTTP 请求,传统的 URLConnection 方法可能会在未来被弃用。这个问题的标题应该改为 如何在 Java 中执行 HTTP 请求 - Mahozad
很多你的问题在HttpURLConnection的Javadoc中已经有了答案。 - undefined
没错。然而,有些人更喜欢具体的代码示例,因为他们并不总能将Javadoc中那些对他们来说晦涩难懂的措辞转化为正常运行的代码。这也是Stack Overflow存在的原因之一。 - undefined
12个回答

2842

首先需要说明的是:所贴出的代码片段都是基础示例。你需要自己处理一些琐碎的IOExceptionRuntimeException,例如NullPointerExceptionArrayIndexOutOfBoundsException等。

如果你是为Android开发而不是Java开发,请注意自API 28引入以来,明文HTTP请求默认已被禁用。建议使用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格式,并使用&连接。通常还需要使用指定字符集的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 = 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。标准的HTTP POST,就像Web表单一样,属于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对以及您想要以编程方式“按下”的 <input type="submit"> 元素的 name=value对添加到查询字符串中(因为通常在服务器端使用它来区分按钮是否被按下,如果是的话,按下哪个按钮)。
您还可以将获得的 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();
  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请求之前,您需要准备一个带有ACCEPT_ALL CookiePolicyCookieManager

// 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请求(例如上传文件)时,这可能会导致OutOfMemoryException异常。为避免这种情况,你需要设置HttpURLConnection#setFixedLengthStreamingMode()

httpConnection.setFixedLengthStreamingMode(contentLength);

但是如果内容长度事先真的不知道,那么您可以通过设置HttpURLConnection#setChunkedStreamingMode()来利用分块流模式。这将设置HTTP Transfer-Encoding头为chunked,从而强制请求体以块的形式发送。下面的示例将以1 KB的块发送主体。

httpConnection.setChunkedStreamingMode(1024);

User-Agent

有时候,请求返回的响应与真实的Web浏览器相比出现了意外的情况。服务器端可能会根据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.

使用来自最近的浏览器的用户代理字符串。


错误处理

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

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

如果HTTP响应代码为-1,则连接和响应处理出现了问题。在较旧的JRE中,HttpURLConnection实现在保持连接方面存在一些错误。您可能希望通过将系统属性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月),如果谷歌检测到不安全的主机名验证器,他们将不允许您的应用程序在Play商店上分发,详情请参见https://support.google.com/faqs/answer/7188426.

有时您需要连接HTTPS URL,可能是因为您正在编写网络爬虫。在这种情况下,您可能会遇到javax.net.ssl.SSLException: Not trusted server certificate,这是由于某些HTTPS网站没有及时更新其SSL证书,或者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 在这方面要方便得多 :)


解析和提取HTML

如果你只想解析和提取HTML数据,最好使用类似于Jsoup这样的HTML解析器。


1
@imperator:这是服务器代码中的一个错误。它抛出了一个异常。可能包含详细异常信息的错误页面可在getErrorStream()中获得。请参阅上面答案中的“错误处理”部分。或者,如果这是您自己的服务器,请阅读其服务器日志。 - BalusC
3
请阅读规范。 -- 部分不是边界的一部分。它只是一个分隔符字符串。我已经撤销了您无效的编辑。 - BalusC
10
令人遗憾的是,在Android上现在不建议使用Apache HttpClient,而HttpURLConnection则很困难。http://android-developers.blogspot.in/2011/09/androids-http-clients.html - yati sagade
1
我找不到Files.copy()和binaryFile.toPath()这些方法。 - Haresh Chhelana
2
@Haresh:它们是Java 7的一部分,就像那些try-with-resources语句一样。也许你仍在使用长时间停止支持的Java 6甚至更早版本?只需升级到至少Java 7(我们目前已经使用Java 8),或者用旧版Java等效代码替换Java 7代码。例如,使用老式的for循环将缓冲区从输入写入输出。 - BalusC
显示剩余19条评论

96

在处理 HTTP 时,通常更有用的是使用 HttpURLConnection 而不是基类 URLConnection(因为当你对 HTTP URL 请求 URLConnection.openConnection() 时会返回一个抽象类 URLConnection 对象)。

然后你可以使用 httpURLConnection.setRequestMethod("POST") 来代替依赖于 URLConnection#setDoOutput(true) 隐式地将请求方法设置为 POST。这样做可能更加自然,并且还允许你指定其他请求方法,如 PUTDELETE 等。

它还提供了一些有用的 HTTP 常量,使你可以执行如下操作:

int responseCode = httpURLConnection.getResponseCode();

if (responseCode == HttpURLConnection.HTTP_OK) {

1
setDoOutPut true 是我的问题,它把我的 GET 请求变成了 POST 请求。谢谢。 - Patrick Kafka
25
如果你想往输出流写入数据,你仍然必须将 setDoOutput() 设置为 true,否则会抛出异常(即使你已经设置了 setRequestMethod("POST"))。需要明确的是:将 URLConnection#setDoOutput(true) 设置为 true 隐式地将请求方法设置为 POST,但是将 httpURLConnection.setRequestMethod("POST") 设置为 POST 不会 隐式地将 setDoOutput() 设置为 true - Tony Chan
2
8年后,setRequestMethod("POST")确实将doOutput设置为**true**。 - Alex Cohn

54

受到Stack Overflow和其他相关问题的启发,我创建了一个最小的开源 basic-http-client ,其中包含了这里发现的大部分技术。

google-http-java-client 也是一个非常好的开源资源。


我也是这么想的。但是,拥有一个仅使用URLConnection代码的基本/简单Java库可能也很不错,该库将封装代码为更简单的方法以执行HTTP GET、POST等操作。然后可以将该库编译和打包为JAR文件,并在Java代码中导入/使用,或者如果不需要外部JAR,则可以将源类文件包含在Java项目中。这可以使用其他库(如Apache等)完成,但与使用URLConnection的简单1个文件类库相比,会更加麻烦。 - David
http://www.rapidvaluesolutions.com/tech_blog/introduction-to-httpurlconnection-http-client-for-performing-efficient-network-operations/ 倾向于使用HttpURLConnection而不是HttpClient。 - Ravindra babu

28

更新

Java 9中的新HTTP客户端已发布,但作为孵化器模块的一部分命名为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;
}

3
更新:该模块已经退出孵化状态。它现在被称为java.net.http,而不是jdk.incubator.httpclient。 - VGR

28

你有两个选项可以使用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()));

4
你如何查看实际的 JSON 响应? - Sora
有七个选项:GET、POST、HEAD、OPTIONS、PUT、DELETE、TRACE。 - undefined

28

建议您查看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);

23

我也受到了这个回答的很多启发。

我经常参与一些需要进行 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;
    }
}

Then post...

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;
}

Well, you get the idea....

Here are the tests:

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

我的目标是以比传统方法更加简便的方式提供常见的IT技术操作...


2
doPost方法中有一个charset参数,用于设置请求头,但是数据却使用了一些硬编码的字符集IO.CHARSET写入。这是一个错误吗? - Vit Khudenko

19

起初我被 这篇文章 误导,它更青睐于 HttpClient

后来我从 这篇文章 中意识到 HttpURLConnection 将会持续存在。

根据 Google 博客所述

Apache HTTP 客户端在 Eclair 和 Froyo 中出现的错误较少。这是这些发行版的最佳选择。对于 Gingerbread,HttpURLConnection 是最好的选择。其简单的 API 和小巧的大小使其非常适合 Android。

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

阅读了 这篇文章 和其他一些 Stack Overflow 的问题后,我相信 HttpURLConnection 将会持续较长时间。

一些支持 HttpURLConnections 的 SE 问题:

在 Android 上,不使用 UrlEncodedFormEntity 进行 URL 编码表单数据的 POST 请求

HttpPost 在 Java 项目中可以工作,但在 Android 上却不能工作


17

还有 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 的文档


16
如果您正在使用HTTP的GET请求方法,请移除此行代码:
urlConnection.setDoOutput(true);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接