Как использовать java.сеть.URLConnection для запуска и обработки HTTP-запросов

использование java.net.URLConnection спрашивают о довольно часто здесь, и в Oracle учебник is слишком кратко об этом.

этот учебник в основном показывает, как запустить запрос GET и прочитать ответ. Он нигде не объясняет, как использовать его для выполнения запроса POST, установки заголовков запросов, чтения заголовков ответов, работы с куки, отправки HTML-формы, загрузки файла и т. д.

Итак, как я могу использовать java.net.URLConnection огонь и обрабатывать "продвинутые" HTTP-запросы?

11 ответов


сначала оговорка заранее: размещенные фрагменты кода являются основными примерами. Вам нужно будет справиться с trivial IOExceptions и RuntimeExceptionкак NullPointerException, ArrayIndexOutOfBoundsException и сами супруги.


подготовка

сначала нам нужно знать хотя бы 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));

параметры запроса должны быть в и быть сцепленным &. Вы бы обычно также URL-кодирование параметры запроса с указанной кодировкой с помощью URLEncoder#encode().

на String#format() - это просто для удобства. Я предпочитаю, когда мне понадобится оператор конкатенации + более чем в два раза.


увольнение HTTP GET запрос (при необходимости) параметры запроса

это тривиальная задача. Это метод запроса по умолчанию.

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

любая строка запроса должна быть объединена с URL-адресом с помощью ?. The Accept-Charset заголовок может подсказать серверу, в какой кодировке находятся параметры. Если вы не отправляете строку запроса, вы можете оставить Accept-Charset заголовок подальше. Если вам не нужно устанавливать какие-либо заголовки, вы даже можете использовать URL#openStream() ярлык способ.

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

в любом случае, если другая сторона HttpServlet, то doGet() будет вызван метод, и параметры будут доступны по HttpServletRequest#getParameter().

для целей тестирования вы можете распечатать тело ответа на stdout, как показано ниже:

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

увольнение HTTP POST запрос с параметрами запроса

задание URLConnection#setDoOutput() to true неявно задает метод запроса POST. Стандарт HTTP POST, как веб-формы, имеет тип application/x-www-form-urlencoded в котором строка запроса записывается в тело запроса.

URLConnection connection = new URL(url).openConnection();
connection.setDoOutput(true); // Triggers POST.
connection.setRequestProperty("Accept-Charset", charset);
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=" + charset);

try (OutputStream output = connection.getOutputStream()) {
    output.write(query.getBytes(charset));
}

InputStream response = connection.getInputStream();
// ...

Примечание: всякий раз, когда вы хотите отправить HTML-форму программно, не забудьте взять name=value пар любого <input type="hidden"> элементы в строку запроса и конечно же name=value пара <input type="submit"> элемент, который вы хотели бы "нажать" программно (потому что это обычно используется на стороне сервера, чтобы отличить, была ли нажата кнопка, и если да, который.)

вы также можете применять полученные URLConnection to HttpURLConnection и использовать его HttpURLConnection#setRequestMethod() вместо. Но если вы пытаетесь использовать соединение для вывода, вам все равно нужно установить URLConnection#setDoOutput() to true.

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

в любом случае, если другая сторона HttpServlet, то doPost() метод будет вызван и параметры будут доступны мимо HttpServletRequest#getParameter().


фактически запуск HTTP-запроса

вы можете запустить HTTP-запрос явно с помощью URLConnection#connect(), но запрос будет автоматически запущен по требованию, когда вы хотите получить любую информацию о HTTP-ответе, например, тело ответа с помощью URLConnection#getInputStream() и так далее. Приведенные выше примеры делают именно это, поэтому connect() звонок на самом деле излишний.


сбор информации HTTP-ответа

  1. состояние ответа HTTP:

    вам понадобится HttpURLConnection здесь. Бросьте его первым, если необходимо.

    int status = httpConnection.getResponseCode();
    
  2. заголовки ответов HTTP:

    for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
        System.out.println(header.getKey() + "=" + header.getValue());
    }
    
  3. кодировка ответа HTTP:

    когда 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. Некоторые веб-формы требуют, чтобы вы вошли в систему и/или отслеживает сессии. Вы можете использовать CookieHandler API для обслуживания cookies. Вам нужно подготовить CookieManager с CookiePolicy of ACCEPT_ALL перед отправкой всех HTTP-запросов.

// First set the default cookie manager.
CookieHandler.setDefault(new CookieManager(null, CookiePolicy.ACCEPT_ALL));

// All the following subsequent URLConnections will use the same cookie manager.
URLConnection connection = new URL(url).openConnection();
// ...

connection = new URL(url).openConnection();
// ...

connection = new URL(url).openConnection();
// ...

обратите внимание, что это, как известно, не всегда работает должным образом во всех обстоятельствах. Если это не удается для вас, то лучше всего вручную собрать и установить заголовки файлов cookie. Вам в основном нужно захватить все Set-Cookie заголовки из ответа логина или первого GET запрос, а затем передайте это через следующий запросы.

// Gather all cookies on the first request.
URLConnection connection = new URL(url).openConnection();
List<String> cookies = connection.getHeaderFields().get("Set-Cookie");
// ...

// Then use the same cookies on all subsequent requests.
connection = new URL(url).openConnection();
for (String cookie : cookies) {
    connection.addRequestProperty("Cookie", cookie.split(";", 2)[0]);
}
// ...

на split(";", 2)[0] есть ли, чтобы избавиться от атрибутов cookie, которые не имеют отношения к стороне сервера, как expires, path, etc. Кроме того, вы также можете использовать cookie.substring(0, cookie.indexOf(';')) вместо split().


режим потоковой передачи

на HttpURLConnection будет по умолчанию буфером весь тело запроса перед фактической отправкой, независимо от того, установили ли вы фиксированную длину контента самостоятельно, используя connection.setRequestProperty("Content-Length", contentLength);. Это может вызвать OutOfMemoryExceptions всякий раз, когда вы одновременно отправляете большие почтовые запросы (например, загрузка файлов). Чтобы избежать этого, вы хотели бы установить HttpURLConnection#setFixedLengthStreamingMode().

httpConnection.setFixedLengthStreamingMode(contentLength);

но если длина контента действительно не известна заранее, то вы можете использовать режим потоковой передачи с фрагментами, установив HttpURLConnection#setChunkedStreamingMode() соответственно. Это установит HTTP Transfer-Encoding


при работе с HTTP почти всегда полезнее ссылаться на HttpURLConnection вместо базового класса URLConnection (поскольку URLConnection является абстрактным классом, когда вы просите URLConnection.openConnection() на HTTP-URL это то, что вы получите в любом случае).

тогда вы можете вместо того, чтобы полагаться на URLConnection#setDoOutput(true) явно задать метод запрос в должности вместо httpURLConnection.setRequestMethod("POST") который некоторые могут найти более естественным (и который также позволяет указать другие методы запроса, такие как поставить, удалить, ...).

он также предоставляет полезные константы HTTP, так что вы можете сделать:

int responseCode = httpURLConnection.getResponseCode();

if (responseCode == HttpURLConnection.HTTP_OK) {

вдохновленный этим и другими вопросами на SO, я создал минимальный открытый исходный код basic-http-client это воплощает большинство методов, найденных здесь.

google-http-java-клиент также является отличным ресурсом с открытым исходным кодом.


есть 2 варианта вы можете пойти с HTTP URL хиты: 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()));

Я предлагаю вам взглянуть на код kevinsawicki / http-запрос, его в основном обертка поверх HttpUrlConnection Он предоставляет гораздо более простой API, если вы просто хотите сделать запросы прямо сейчас или вы можете взглянуть на источники (это не слишком большой), чтобы посмотреть, как обрабатываются соединения.

пример: сделать GET запрос типа контента application/json и некоторые параметры запроса:

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

Я был очень вдохновлен этим ответом.

Я часто на проектах, где мне нужно сделать некоторые HTTP, и я не могу принести много зависимостей 3rd (которые приносят другие и так далее и так далее и т. д.)

Я начал писать свои собственные утилиты, основанные на некоторых из этого разговора (а не где-либо):

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

моя цель-предоставить общие вещи, которые вы хотели бы сделать немного проще....


обновление

новый HTTP-клиент поставляется с Java 9, но как часть Модуль инкубатораjdk.incubator.httpclient. Модули инкубатора средство сдачи не-окончательных API в руки разработчиков в то время как APIs продвигается к завершению или удалению в будущем освобождать.

в 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 com.foo.bar {
    requires jdk.incubator.httpclient;
}

Первоначально я был введен в заблуждение этим статьи выступает HttpClient.

позже я понял, что HttpURLConnection собирается остаться от этого статьи

согласно блогу Google:

Apache HTTP client имеет меньше ошибок на Eclair и Froyo. Это лучший выбор для этих релизов. Для пряников HttpURLConnection-лучший выбор. Его простой API и небольшой размер делают его отличным для Андроид.

прозрачное сжатие и кэширование ответов уменьшают использование сети, улучшают скорость и экономят батарею. Новые приложения должны использовать HttpURLConnection; именно там мы будем тратить нашу энергию в будущем.

после прочтения в этой статье и некоторые другие вопросы стека над потоком, я убежден, что HttpURLConnection собирается остаться дольше.

некоторые из вопросов SE в пользу HttpURLConnections:

на Android сделайте запрос POST с данными url-кодированной формы без использования UrlEncodedFormEntity

HttpPost работает в проекте Java, а не в Android


вы также можете использовать JdkRequest С jcabi-http (Я разработчик), который делает всю эту работу для вас, украшая HttpURLConnection, запуская HTTP-запросы и анализируя ответы, например:

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

проверьте это сообщение в блоге для получения дополнительной информации:http://www.yegor256.com/2014/04/11/jcabi-http-intro.html


появилась 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


Если вы используете http get, удалите эту строку

urlConnection.setDoOutput(true);