简体   繁体   English

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

[英]How to use java.net.URLConnection to fire and handle HTTP requests

Use of java.net.URLConnection is asked about pretty often here, and the Oracle tutorial is too concise about it. java.net.URLConnection使用在这里经常被问到,而Oracle 教程对此过于简洁。

That tutorial basically only shows how to fire a GET request and read the response.该教程基本上只展示了如何触发GET请求并读取响应。 It doesn't explain anywhere how to use it to, among others, perform a POST request, set request headers, read response headers, deal with cookies, submit a HTML form, upload a file, etc.它没有在任何地方解释如何使用它来执行POST请求、设置请求标头、读取响应标头、处理 cookie、提交 HTML 表单、上传文件等。

So, how can I use java.net.URLConnection to fire and handle "advanced" HTTP requests?那么,如何使用java.net.URLConnection来触发和处理“高级”HTTP 请求?

First a disclaimer beforehand: the posted code snippets are all basic examples.首先事先声明:发布的代码片段都是基本示例。 You'll need to handle trivial IOException s and RuntimeException s like NullPointerException , ArrayIndexOutOfBoundsException and consorts yourself.您需要处理琐碎的IOExceptionRuntimeExceptionNullPointerExceptionArrayIndexOutOfBoundsException和自己。

In case you're developing for Android instead of Java, note also that since introduction of API level 28, cleartext HTTP requests are disabled by default .如果您正在为 Android 而不是 Java 进行开发,还要注意,自从引入 API 级别 28 以来,默认情况下禁用明文 HTTP 请求。 You are encouraged to use HttpsURLConnection , but if it is really necessary, cleartext can be enabled in the Application Manifest.鼓励您使用HttpsURLConnection ,但如果确实有必要,可以在应用程序清单中启用明文。


Preparing准备

We first need to know at least the URL and the charset.我们首先至少需要知道 URL 和字符集。 The parameters are optional and depend on the functional requirements.这些参数是可选的,取决于功能要求。

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

The query parameters must be in name=value format and be concatenated by & .查询参数必须采用name=value格式并由&连接。 You would normally also URL-encode the query parameters with the specified charset using URLEncoder#encode() .您通常还会使用URLEncoder#encode()使用指定的字符集对查询参数进行 URL 编码

The String#format() is just for convenience. String#format()只是为了方便。 I prefer it when I would need the String concatenation operator + more than twice.当我需要字符串连接运算符+两次以上时,我更喜欢它。


Firing an HTTP GET request with (optionally) query parameters使用(可选)查询参数触发HTTP GET请求

It's a trivial task.这是一项微不足道的任务。 It's the default request method.这是默认的请求方法。

URLConnection connection = new URL(url + "?" + query).openConnection();
connection.setRequestProperty("Accept-Charset", charset);
InputStream response = connection.getInputStream();
// ...

Any query string should be concatenated to the URL using ?任何查询字符串都应该使用? . . The Accept-Charset header may hint the server what encoding the parameters are in. If you don't send any query string, then you can leave the Accept-Charset header away. Accept-Charset标头可能会提示服务器参数的编码方式。如果您不发送任何查询字符串,那么您可以离开Accept-Charset标头。 If you don't need to set any headers, then you can even use the URL#openStream() shortcut method.如果您不需要设置任何标题,那么您甚至可以使用URL#openStream()快捷方法。

InputStream response = new URL(url).openStream();
// ...

Either way, if the other side is an HttpServlet , then its doGet() method will be called and the parameters will be available by HttpServletRequest#getParameter() .无论哪种方式,如果另一端是HttpServlet ,那么它的doGet()方法将被调用并且参数将通过HttpServletRequest#getParameter()可用。

For testing purposes, you can print the response body to standard output as below:出于测试目的,您可以将响应正文打印到标准输出,如下所示:

try (Scanner scanner = new Scanner(response)) {
    String responseBody = scanner.useDelimiter("\\A").next();
    System.out.println(responseBody);
}

Firing an HTTP POST request with query parameters使用查询参数触发HTTP POST请求

Setting the URLConnection#setDoOutput() to true implicitly sets the request method to POST.URLConnection#setDoOutput()true隐式地将请求方法设置为 POST。 The standard HTTP POST as web forms do is of type application/x-www-form-urlencoded wherein the query string is written to the request body. 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();
// ...

Note: whenever you'd like to submit a HTML form programmatically, don't forget to take the name=value pairs of any <input type="hidden"> elements into the query string and of course also the name=value pair of the <input type="submit"> element which you'd like to "press" programmatically (because that's usually been used in the server side to distinguish if a button was pressed and if so, which one).注意:当你想以编程方式提交HTML表单,不要忘记采取name=value任何对<input type="hidden">元素融入到查询字符串,当然还有的name=value对您希望以编程方式“按下”的<input type="submit">元素(因为通常在服务器端使用它来区分按钮是否被按下,如果按下,是哪个)。

You can also cast the obtained URLConnection to HttpURLConnection and use its HttpURLConnection#setRequestMethod() instead.您还可以将获得的URLConnectionHttpURLConnection并使用其HttpURLConnection#setRequestMethod()代替。 But if you're trying to use the connection for output you still need to set URLConnection#setDoOutput() to true .但是,如果您尝试使用连接进行输出,您仍然需要将URLConnection#setDoOutput()true

HttpURLConnection httpConnection = (HttpURLConnection) new URL(url).openConnection();
httpConnection.setRequestMethod("POST");
// ...

Either way, if the other side is an HttpServlet , then its doPost() method will be called and the parameters will be available by HttpServletRequest#getParameter() .无论哪种方式,如果另一端是HttpServlet ,那么它的doPost()方法将被调用并且参数将通过HttpServletRequest#getParameter()可用。


Actually firing the HTTP request实际触发 HTTP 请求

You can fire the HTTP request explicitly with URLConnection#connect() , but the request will automatically be fired on demand when you want to get any information about the HTTP response, such as the response body using URLConnection#getInputStream() and so on.您可以使用URLConnection#connect()显式触发 HTTP 请求,但是当您想要获取有关 HTTP 响应的任何信息(例如使用URLConnection#getInputStream()等的响应正文URLConnection#connect() ,该请求将根据需要自动触发。 The above examples does exactly that, so the connect() call is in fact superfluous.上面的例子正是这样做的,所以connect()调用实际上是多余的。


Gathering HTTP response information收集 HTTP 响应信息

  1. HTTP response status : HTTP 响应状态

You need an HttpURLConnection here.你需要一个HttpURLConnection在这里。 Cast it first if necessary.如有必要,请先施放。

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

     for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) { System.out.println(header.getKey() + "=" + header.getValue()); }
  2. HTTP response encoding : HTTP 响应编码

When the Content-Type contains a charset parameter, then the response body is likely text based and we'd like to process the response body with the server-side specified character encoding then.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.
    }

Maintaining the session维护会话

The server side session is usually backed by a cookie.服务器端会话通常由 cookie 支持。 Some web forms require that you're logged in and/or are tracked by a session.某些 Web 表单要求您登录和/或被会话跟踪。 You can use the CookieHandler API to maintain cookies.您可以使用CookieHandler API 来维护 cookie。 You need to prepare a CookieManager with a CookiePolicy of ACCEPT_ALL before sending all HTTP requests.在发送所有 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();
// ...

Note that this is known to not always work properly in all circumstances.请注意,众所周知,这并不总是在所有情况下都能正常工作。 If it fails for you, then best is to manually gather and set the cookie headers.如果它对您来说失败了,那么最好是手动收集和设置 cookie 标头。 You basically need to grab all Set-Cookie headers from the response of the login or the first GET request and then pass this through the subsequent requests.您基本上需要从登录或第一个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]);
}
// ...

The split(";", 2)[0] is there to get rid of cookie attributes which are irrelevant for the server side like expires , path , etc. Alternatively, you could also use cookie.substring(0, cookie.indexOf(';')) instead of split() . split(";", 2)[0]是为了摆脱与服务器端无关的 cookie 属性,如expirespath等。或者,您也可以使用cookie.substring(0, cookie.indexOf(';'))而不是split()


Streaming mode流媒体模式

The HttpURLConnection will by default buffer the entire request body before actually sending it, regardless of whether you've set a fixed content length yourself using connection.setRequestProperty("Content-Length", contentLength); HttpURLConnection将默认在实际发送之前缓冲整个请求正文,无论您是否使用connection.setRequestProperty("Content-Length", contentLength);自己设置了固定的内容长度connection.setRequestProperty("Content-Length", contentLength); . . This may cause OutOfMemoryException s whenever you concurrently send large POST requests (eg uploading files).每当您同时发送大型 POST 请求(例如上传文件)时,这可能会导致OutOfMemoryException To avoid this, you would like to set the HttpURLConnection#setFixedLengthStreamingMode() .为避免这种情况,您需要设置HttpURLConnection#setFixedLengthStreamingMode()

httpConnection.setFixedLengthStreamingMode(contentLength);

But if the content length is really not known beforehand, then you can make use of chunked streaming mode by setting the HttpURLConnection#setChunkedStreamingMode() accordingly.但是如果内容长度真的事先不知道,那么您可以通过相应地设置HttpURLConnection#setChunkedStreamingMode()来使用分块流模式。 This will set the HTTPTransfer-Encoding header to chunked which will force the request body being sent in chunks.这会将 HTTPTransfer-Encoding标头设置为chunked ,这将强制以chunked形式发送请求正文。 The below example will send the body in chunks of 1 KB.下面的示例将以 1 KB 的块发送正文。

httpConnection.setChunkedStreamingMode(1024);

User-Agent用户代理

It can happen that a request returns an unexpected response, while it works fine with a real web browser . 一个请求可能会返回一个意外的响应,而它在真实的 Web 浏览器中工作正常 The server side is probably blocking requests based on theUser-Agent request header.服务器端可能会根据User-Agent请求标头阻止请求。 The URLConnection will by default set it to Java/1.6.0_19 where the last part is obviously the JRE version.默认情况下, URLConnection会将其设置为Java/1.6.0_19 ,其中最后一部分显然是 JRE 版本。 You can override this as follows:您可以按如下方式覆盖它:

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.

Use the User-Agent string from a recent browser .使用来自最近浏览器的 User-Agent 字符串。


Error handling错误处理

If the HTTP response code is 4nn (Client Error) or 5nn (Server Error), then you may want to read the HttpURLConnection#getErrorStream() to see if the server has sent any useful error information.如果 HTTP 响应代码是4nn (客户端错误)或5nn (服务器错误),那么您可能需要阅读HttpURLConnection#getErrorStream()以查看服务器是否发送了任何有用的错误信息。

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

If the HTTP response code is -1, then something went wrong with connection and response handling.如果 HTTP 响应代码为 -1,则连接和响应处理出现问题。 The HttpURLConnection implementation is in older JREs somewhat buggy with keeping connections alive. HttpURLConnection实现在较旧的 JRE 中在保持连接活动方面有些问题。 You may want to turn it off by setting the http.keepAlive system property to false .您可能希望通过将http.keepAlive系统属性设置为false来关闭它。 You can do this programmatically in the beginning of your application by:您可以通过以下方式在应用程序开始时以编程方式执行此操作:

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

Uploading files上传文件

You'd normally use multipart/form-data encoding for mixed POST content (binary and character data).您通常会对混合的 POST 内容(二进制和字符数据)使用multipart/form-data编码。 The encoding is in more detail described in RFC2388 .编码在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();
}

If the other side is an HttpServlet , then its doPost() method will be called and the parts will be available by HttpServletRequest#getPart() (note, thus not getParameter() and so on!).如果另一端是一个HttpServlet ,那么它的doPost()方法将被调用并且这些部分将通过HttpServletRequest#getPart()可用(注意,因此不是getParameter()等等!)。 The getPart() method is however relatively new, it's introduced in Servlet 3.0 (Glassfish 3, Tomcat 7, etc.).然而getPart()方法相对较新,它是在 Servlet 3.0(Glassfish 3、Tomcat 7 等)中引入的。 Prior to Servlet 3.0, your best choice is using Apache Commons FileUpload to parse a multipart/form-data request.在 Servlet 3.0 之前,最好的选择是使用Apache Commons FileUpload来解析multipart/form-data请求。 Also see this answer for examples of both the FileUpload and the Servelt 3.0 approaches.有关 FileUpload 和 Servelt 3.0 方法的示例,另请参阅此答案


Dealing with untrusted or misconfigured HTTPS sites处理不受信任或配置错误的 HTTPS 站点

In case you're developing for Android instead of Java, be careful : the workaround below may save your day if you don't have correct certificates deployed during development.如果您正在为 Android 而不是 Java 进行开发,请注意:如果您在开发过程中没有部署正确的证书,下面的解决方法可能会节省您的时间。 But you should not use it for production.但是您不应该将它用于生产。 These days (April 2021) Google will not allow your app be distributed on Play Store if they detect insecure hostname verifier, see https://support.google.com/faqs/answer/7188426.这些天(2021 年 4 月)如果 Google 检测到不安全的主机名验证程序,他们将不允许您的应用在 Play 商店中分发,请参阅https://support.google.com/faqs/answer/7188426。

Sometimes you need to connect an HTTPS URL, perhaps because you're writing a web scraper.有时您需要连接 HTTPS URL,可能是因为您正在编写网络爬虫。 In that case, you may likely face a javax.net.ssl.SSLException: Not trusted server certificate on some HTTPS sites who doesn't keep their SSL certificates up to date, or a java.security.cert.CertificateException: No subject alternative DNS name matching [hostname] found or javax.net.ssl.SSLProtocolException: handshake alert: unrecognized_name on some misconfigured HTTPS sites.在这种情况下,您可能会遇到javax.net.ssl.SSLException: Not trusted server certificate on some HTTPS 站点,他们的 SSL 证书没有保持最新,或者java.security.cert.CertificateException: No subject alternative DNS name matching [hostname] foundjavax.net.ssl.SSLProtocolException: handshake alert: unrecognized_name某些配置错误的 HTTPS 站点上的javax.net.ssl.SSLProtocolException: handshake alert: unrecognized_name

The following one-time-run static initializer in your web scraper class should make HttpsURLConnection more lenient as to those HTTPS sites and thus not throw those exceptions anymore.您的网络抓取工具类中的以下一次性运行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);
    }
}

Last words最后的话

The Apache HttpComponents HttpClient is much more convenient in this all :) Apache HttpComponents HttpClient在这方面方便得多:)


Parsing and extracting HTML解析和提取 HTML

If all you want is parsing and extracting data from HTML, then better use a HTML parser like Jsoup .如果您只想从 HTML 解析和提取数据,那么最好使用像Jsoup这样的 HTML 解析器。

When working with HTTP it's almost always more useful to refer to HttpURLConnection rather than the base class URLConnection (since URLConnection is an abstract class when you ask for URLConnection.openConnection() on a HTTP URL that's what you'll get back anyway).在使用 HTTP 时,引用HttpURLConnection而不是基类URLConnection几乎总是更有用(因为当您在 HTTP URL 上请求URLConnection.openConnection()时, URLConnection是一个抽象类,无论如何您都会得到它)。

Then you can instead of relying on URLConnection#setDoOutput(true) to implicitly set the request method to POST instead do httpURLConnection.setRequestMethod("POST") which some might find more natural (and which also allows you to specify other request methods such as PUT , DELETE , ...).然后,您可以不依赖URLConnection#setDoOutput(true)将请求方法隐式设置为POST而是执行httpURLConnection.setRequestMethod("POST")这有些人可能会觉得更自然(并且还允许您指定其他请求方法,例如放置删除,...)。

It also provides useful HTTP constants so you can do:它还提供了有用的 HTTP 常量,因此您可以执行以下操作:

int responseCode = httpURLConnection.getResponseCode();

if (responseCode == HttpURLConnection.HTTP_OK) {

Inspired by this and other questions on Stack Overflow, I've created a minimal open source basic-http-client that embodies most of the techniques found here.受 Stack Overflow 上的这个问题和其他问题的启发,我创建了一个最小的开源basic-http-client ,它包含了这里找到的大部分技术。

google-http-java-client is also a great open source resource. google-http-java-client也是一个很棒的开源资源。

I suggest you take a look at the code on kevinsawicki/http-request , its basically a wrapper on top of HttpUrlConnection it provides a much simpler API in case you just want to make the requests right now or you can take a look at the sources (it's not too big) to take a look at how connections are handled.我建议您查看kevinsawicki/http-request上的代码,它基本上是HttpUrlConnection之上的包装器,它提供了一个更简单的 API,以防您只想立即发出请求,或者您可以查看源代码(不是太大)来看看连接是如何处理的。

Example: Make a GET request with content type application/json and some query parameters:示例:使用内容类型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);

Update更新

The new HTTP Client shipped with Java 9 but as part of an Incubator module named jdk.incubator.httpclient .新的 HTTP 客户端随 Java 9 一起提供,但作为名为jdk.incubator.httpclient的 Incubator 模块的jdk.incubator.httpclient Incubator modules are a means of putting non-final APIs in the hands of developers while the APIs progress towards either finalization or removal in a future release.孵化器模块是一种将非最终 API 交给开发人员的方法,同时这些 API 会在未来版本中逐步确定或删除。

In Java 9, you can send a GET request like:在 Java 9 中,您可以发送一个GET请求,如:

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

Then you can examine the returned HttpResponse :然后你可以检查返回的HttpResponse

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

Since this new HTTP Client is in由于这个新的 HTTP 客户端在java.httpclient jdk.incubator.httpclient module, you should declare this dependency in your module-info.java file: jdk.incubator.httpclient模块,你应该在你的module-info.java文件中声明这个依赖:

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

There are two options you can go with HTTP URL Hits : GET / POST HTTP URL Hits 有两个选项:GET / POST

GET Request:获取请求:

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 request: 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()));

I was also very inspired by this response.我也很受这个回应的启发。

I am often on projects where I need to do some HTTP, and I may not want to bring in a lot of third-party dependencies (which bring in others and so on and so on, etc.)我经常在需要做一些 HTTP 的项目中,我可能不想引入很多第三方依赖项(引入其他依赖项等等)

I started to write my own utilities based on some of this conversation (not any where done):我开始根据这些对话中的一些内容(不是任何地方完成的)编写自己的实用程序:

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 {

Then there are just a bunch or static methods.然后只有一堆或静态方法。

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

You can find the rest here:你可以在这里找到其余的:

https://github.com/RichardHightower/boon https://github.com/RichardHightower/boon

My goal is to provide the common things one would want to do in a bit more easier way then....我的目标是以更简单的方式提供人们想要做的常见事情......

Initially I was misled by this article which favours HttpClient .最初我被这篇偏爱HttpClient 文章误导了。

Later I have realized that HttpURLConnection is going to stay from this article .后来我意识到HttpURLConnection将保留在本文中

As per the Google blog :根据谷歌博客

Apache HTTP client has fewer bugs on Eclair and Froyo. Apache HTTP 客户端在 Eclair 和 Froyo 上的错误较少。 It is the best choice for these releases.它是这些版本的最佳选择。 For Gingerbread , HttpURLConnection is the best choice.对于 Gingerbread , HttpURLConnection 是最佳选择。 Its simple API and small size makes it great fit for Android.其简单的 API 和小尺寸使其非常适合 Android。

Transparent compression and response caching reduce network use, improve speed and save battery.透明压缩和响应缓存减少网络使用,提高速度并节省电池。 New applications should use HttpURLConnection;新的应用程序应该使用 HttpURLConnection; it is where we will be spending our energy going forward.这是我们将在未来花费精力的地方。

After reading this article and some other stack over flow questions, I am convinced that HttpURLConnection is going to stay for longer durations.在阅读了这篇文章和其他一些关于流的堆栈问题后,我确信HttpURLConnection会持续更长时间。

Some of the SE questions favouring HttpURLConnections :一些支持HttpURLConnections的 SE 问题:

On Android, make a POST request with URL Encoded Form data without using UrlEncodedFormEntity 在 Android 上,使用 URL Encoded Form 数据发出 POST 请求而不使用 UrlEncodedFormEntity

HttpPost works in Java project, but not on Android HttpPost 适用于 Java 项目,但不适用于 Android

There is also OkHttp , which is an HTTP client that's efficient by default:还有OkHttp ,它是一个默认高效的 HTTP 客户端:

  • HTTP/2 support allows all requests to the same host to share a socket. HTTP/2 支持允许对同一主机的所有请求共享一个套接字。
  • Connection pooling reduces request latency (if HTTP/2 isn't available).连接池减少了请求延迟(如果 HTTP/2 不可用)。
  • Transparent GZIP shrinks download sizes.透明 GZIP 可缩小下载大小。
  • Response caching avoids the network completely for repeat requests.响应缓存完全避免网络重复请求。

First create an instance of OkHttpClient :首先创建一个OkHttpClient实例:

OkHttpClient client = new OkHttpClient();

Then, prepare your GET request:然后,准备您的GET请求:

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

finally, use OkHttpClient to send prepared Request :最后,使用OkHttpClient发送准备好的Request

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

For more details, you can consult the OkHttp's documentation更详细的可以参考OkHttp的文档

You can also use JdkRequest from jcabi-http (I'm a developer), which does all this work for you, decorating HttpURLConnection, firing HTTP requests and parsing responses, for example:您还可以使用JdkRequest -http (我是开发人员)中的JdkRequest ,它为您完成所有这些工作,装饰 HttpURLConnection,触发 HTTP 请求和解析响应,例如:

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

Check this blog post for more info: http://www.yegor256.com/2014/04/11/jcabi-http-intro.html查看此博客文章了解更多信息: http : //www.yegor256.com/2014/04/11/jcabi-http-intro.html

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

urlConnection.setDoOutput(true);

If you are using Java 11 ( except on Android ), instead of the legacy HttpUrlConnection class, you can use Java 11 new HTTP Client API .如果您使用的是Java 11Android 除外),则可以使用 Java 11 新的HTTP Client API ,而不是旧的HttpUrlConnection类。

An example GET request: 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());

The same request executed asynchronously:异步执行相同的请求:

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

An example POST request:一个示例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());

For sending form data as multipart ( multipart/form-data ) or url-encoded ( application/x-www-form-urlencoded ) format, see this solution .要以 multipart ( multipart/form-data ) 或 url-encoded ( application/x-www-form-urlencoded ) 格式发送表单数据,请参阅此解决方案

See this article for examples and more information about HTTP Client API.有关 HTTP 客户端 API 的示例和更多信息,请参阅本文

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM