CLOVER🍀

That was when it all began.

JavaでのHttpClientサンプル

少し前にJAX-RSのクライアントを触ったこともあり、久々にいくつか書いてみたくなりまして。

java.netからJAX-RSまで、いくつかHttpClientのサンプルプログラムを書いてみたいと思います。

サンプルとしては、

です。パラメータのURLエンコーディングとかまでは書いてないので、そこは微妙かも。まあ、こんなのがありますよってことで。

なお、ビルド・実行にはMavenを使用しました。

java.net.URL

Java標準ライブラリを使って書く方法になります。特にMaven依存関係などは不要です。

サンプルソース
src/main/java/httpclient/example/JavaNetHttpClient.java

package httpclient.example;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.net.HttpURLConnection;
import java.nio.charset.StandardCharsets;

public class JavaNetHttpClient {
    public static void main(String[] args) {
        executeGet();
        executePost();
    }

    private static void executeGet() {
        System.out.println("===== HTTP GET Start =====");
        try {
            URL url = new URL("http://localhost:8080/get?param=value");

            HttpURLConnection connection = null;

            try {
                connection = (HttpURLConnection) url.openConnection();
                connection.setRequestMethod("GET");

                if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
                    try (InputStreamReader isr = new InputStreamReader(connection.getInputStream(),
                                                                       StandardCharsets.UTF_8);
                         BufferedReader reader = new BufferedReader(isr)) {
                        String line;
                        while ((line = reader.readLine()) != null) {
                            System.out.println(line);
                        }
                    }
                }
            } finally {
                if (connection != null) {
                    connection.disconnect();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        System.out.println("===== HTTP GET End =====");
    }

    private static void executePost() {
        System.out.println("===== HTTP POST Start =====");
        try {
            URL url = new URL("http://localhost:8080/post");

            HttpURLConnection connection = null;

            try {
                connection = (HttpURLConnection) url.openConnection();
                connection.setDoOutput(true);
                connection.setRequestMethod("POST");

                BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(connection.getOutputStream(),
                                                                                  StandardCharsets.UTF_8));
                writer.write("POST Body");
                writer.write("\r\n");
                writer.write("Hello Http Server!!");
                writer.write("\r\n");
                writer.flush();

                if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
                    try (InputStreamReader isr = new InputStreamReader(connection.getInputStream(),
                                                                       StandardCharsets.UTF_8);
                         BufferedReader reader = new BufferedReader(isr)) {
                        String line;
                        while ((line = reader.readLine()) != null) {
                            System.out.println(line);
                        }
                    }
                }
            } finally {
                if (connection != null) {
                    connection.disconnect();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        System.out.println("===== HTTP POST End =====");
    }
}

java.net.URLのopenConnectionメソッドを使用することで、HttpURLConnectionを取得することができます。HttpURLConnection#getResponseCodeやURLConnection#getInputStreamで、結果を取得することができます。

一応、このクラスでもまあHTTP通信はすることができます。そんなに機能はないので、これを使うことはあまりないかも?

Apache HttpComponents(HttpClient)

たぶん、Javaで1番有名なHTTPクライアントライブラリ。Commons HttpClientの頃から、かなり使われていると思います。

HttpClient
http://hc.apache.org/httpcomponents-client-4.3.x/index.html

4系から、CoreとClientに分かれましたね。

現時点での最新安定版は4.3.1なので、Maven依存関係はこのように定義します。

    <dependency>
      <groupId>org.apache.httpcomponents</groupId>
      <artifactId>httpclient</artifactId>
      <version>4.3.1</version>
    </dependency>

サンプルソース
src/main/java/httpclient/example/HttpComponentsHttpClient.java

package httpclient.example;

import java.io.IOException;
import java.nio.charset.StandardCharsets;

import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.entity.StringEntity;
import org.apache.http.util.EntityUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.client.HttpClientBuilder;

public class HttpComponentsHttpClient {
    public static void main(String[] args) {
        executeGet();
        executePost();
    }

    private static void executeGet() {
        System.out.println("===== HTTP GET Start =====");

        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
        // もしくは
        // try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
            HttpGet getMethod = new HttpGet("http://localhost:8080/get?param=value");

            try (CloseableHttpResponse response = httpClient.execute(getMethod)) {
                if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                    HttpEntity entity = response.getEntity();
                    System.out.println(EntityUtils.toString(entity,
                                                            StandardCharsets.UTF_8));
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        System.out.println("===== HTTP GET End =====");
    }

    private static void executePost() {
        System.out.println("===== HTTP POST Start =====");

        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
        // もしくは
        // try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
            HttpPost postMethod = new HttpPost("http://localhost:8080/post");

            StringBuilder builder = new StringBuilder();
            builder.append("POST Body");
            builder.append("\r\n");
            builder.append("Hello Http Server!!");
            builder.append("\r\n");

            postMethod.setEntity(new StringEntity(builder.toString(),
                                                  StandardCharsets.UTF_8));

            try (CloseableHttpResponse response = httpClient.execute(postMethod)) {
                if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                    HttpEntity entity = response.getEntity();
                    System.out.println(EntityUtils.toString(entity,
                                                            StandardCharsets.UTF_8));
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        System.out.println("===== HTTP POST End =====");
    }
}

以前はDefaultHttpClientとか使っていたと思いますが、ちょっとAPIが変わったようです。最初は知らなくて、普通にDefaultHttpClientを使おうとしたら非推奨の警告が出たので気付きました。

現在は、HttpClientBuilderで作成するか、簡易にHttpClientsから取得するようです。その時の型は、CloseableHttpClientみたいです。レスポンスも、CloseableHttpResponseになっていますが…。

その他、HttpEntityを使ったりするところは変わっていません。

Apache HttpComponents(Fluent API

これは知らなかったです。HttpClientの追加APIみたいなものですが、Quick Startに載っていたので気付きました。

HttpClient Quick Start
http://hc.apache.org/httpcomponents-client-4.3.x/quickstart.html

こんな感じで使うみたいです。

Request.Get("http://targethost/homepage")
    .execute().returnContent();
Request.Post("http://targethost/login")
    .bodyForm(Form.form().add("username",  "vip").add("password",  "secret").build())
    .execute().returnContent();

ドキュメント。

Fluent API
http://hc.apache.org/httpcomponents-client-ga/tutorial/html/fluent.html

Javadoc
https://hc.apache.org/httpcomponents-client-ga/fluent-hc/apidocs/

Maven依存関係は、こんな感じになります。

    <dependency>
      <groupId>org.apache.httpcomponents</groupId>
      <artifactId>fluent-hc</artifactId>
      <version>4.3.1</version>
    </dependency>

HttpClientとは別のアーティファクトが必要です。

サンプルソース
src/main/java/httpclient/example/FluentHttpClient.java

package httpclient.example;

import java.io.IOException;
import java.nio.charset.StandardCharsets;

import org.apache.http.entity.StringEntity;
import org.apache.http.client.fluent.Request;
import org.apache.http.client.fluent.Response;

public class FluentHttpClient {
    public static void main(String[] args) {
        executeGet();
        executePost();
    }

    private static void executeGet() {
        System.out.println("===== HTTP GET Start =====");

        Response response = null;
        try {
            response =
                Request
                .Get("http://localhost:8080/get?param=value")
                .execute();

            System.out.println(new String(response.returnContent().asBytes(),
                                          StandardCharsets.UTF_8));

            // 補足
            // org.apache.http.HttpResonseを取得するには、Response#returnResponseを呼び出せばよい
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (response != null) {
                response.discardContent();
            }
        }

        System.out.println("===== HTTP GET End =====");
    }

    private static void executePost() {
        System.out.println("===== HTTP POST Start =====");

        StringBuilder builder = new StringBuilder();
        builder.append("POST Body");
        builder.append("\r\n");
        builder.append("Http Server!!");
        builder.append("\r\n");

        Response response = null;
        try {
            response =
                Request
                .Post("http://localhost:8080/post")
                .body(new StringEntity(builder.toString(),
                                       (StandardCharsets.UTF_8)))
                .execute();

            System.out.println(new String(response.returnContent().asBytes(),
                                          StandardCharsets.UTF_8));

            // 補足
            // org.apache.http.HttpResonseを取得するには、Response#returnResponseを呼び出せばよい
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (response != null) {
                response.discardContent();
            }
        }

        System.out.println("===== HTTP POST End =====");
    }
}

使用するクラスは、Request、Responseが主なもので、RequestからDSLっぽくHTTPリクエストを実行します。ここではResponseクラスのreturnContentを使用するためにHTTPステータスコードの判定は使っていませんが、先のHttpClientの例と同様の判定を行う場合は、Response#returnResponseでHttpResponseが取得できるので、ここから判定すればよいでしょう。

まあ、HttpClientの例ではCloseableHttpResponseになっていましたが…。

この次に書く、JAX-RSクライアントにちょっと似てる感じがします。

JAX-RS Client

JAX-RS 2.0から加わった、JAX-RSのClient APIです。

JAX-RSの実装には、RESTEasyを使用しました。よく名前を見る、Jerseyではなく。

RESTEasy
http://www.jboss.org/resteasy

JBoss Projectsの中のひとつですね。Mavenの定義しては、まずリポジトリの追加を行います。

    <repository>
      <id>jboss-public-repository</id>
      <name>JBoss Public Repository Group</name>
      <url>http://repository.jboss.org/nexus/content/groups/public-jboss</url>
    </repository>

あと、依存関係。

    <dependency>
      <groupId>org.jboss.resteasy</groupId>
      <artifactId>resteasy-jaxrs</artifactId>
      <version>3.0.4.Final</version>
    </dependency>

    <dependency>
      <groupId>org.jboss.resteasy</groupId>
      <artifactId>resteasy-client</artifactId>
      <version>3.0.4.Final</version>
    </dependency>

ちなみに、Client APIを動かすだけなら、別にリポジトリの追加はなくても大丈夫そうなのですが…。

サンプルコード。
src/main/java/httpclient/example/JaxrsHttpClient.java

package httpclient.example;

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Response;

public class JaxrsHttpClient {
    public static void main(String[] args) {
        executeGet();
        executePost();
    }

    private static void executeGet() {
        System.out.println("===== HTTP GET Start =====");

        Client client = ClientBuilder.newBuilder().build();
        try {
            Response response =
                client
                .target("http://localhost:8080/get?param=value")
                .request()
                .get();

            if (response.getStatus() == Response.Status.OK.getStatusCode()) {
                System.out.println(response.readEntity(String.class));
            }

            response.close();
        } finally {
            client.close();
        }

        System.out.println("===== HTTP GET End =====");
    }

    private static void executePost() {
        System.out.println("===== HTTP POST Start =====");

        StringBuilder builder = new StringBuilder();
        builder.append("POST Body");
        builder.append("\r\n");
        builder.append("Http Server!!");
        builder.append("\r\n");

        Client client = ClientBuilder.newBuilder().build();
        try {
            Response response =
                client
                .target("http://localhost:8080/post")
                .request()
                .post(Entity.text(builder.toString()));

            if (response.getStatus() == Response.Status.OK.getStatusCode()) {
                System.out.println(response.readEntity(String.class));
            }

            response.close();
        } finally {
            client.close();
        }

        System.out.println("===== HTTP POST End =====");
    }
}

ClientBuilderからClientを作成し、あとはDSLっぽくリクエストを組み立てていきます。最後にHTTPの各種メソッドに対応するメソッド(定義はSyncInvokerインターフェース)を呼び出すことで、実行します。

戻り値はResponseクラスのインスタンスとして取得できるので、ここからレスポンスの内容を取得すればOKです。

ところで、どうしてClientはCloseableを実装していないのでしょうか。

とまあ、簡単ですが、こんな感じで。あといくつかネタ的なクライアントライブラリを使おうと思ったのですが、長くなりそうなのでやめました…。

ちなみに、このHTTPリクエストを受けていたサーバは、実はGroovyで書いています。JDKに付属のものを使った、超簡易サーバです。
LightHttpd.groovy

import java.io.IOException
import java.net.InetSocketAddress

import com.sun.net.httpserver.HttpExchange
import com.sun.net.httpserver.HttpHandler
import com.sun.net.httpserver.HttpServer

class SimpleHttpHandler implements HttpHandler {
    @Override
    public void handle(HttpExchange exchange) throws IOException {
        try {
            def builder = new StringBuilder()
            builder << "Accessed URL = ${exchange.requestURI}" << "\r\n"
            builder << "Accessed Method = ${exchange.requestMethod}" << "\r\n"
            builder << "Accessed Date = ${new Date()}" << "\r\n"

            switch (exchange.requestMethod) {
                case "GET":
                    break
                case "POST":
                    builder << "Request Body<<" << "\r\n"
                    builder << exchange.requestBody.getText("UTF-8")
                    builder << ">>" << "\r\n"
            }

            def bytes = builder.toString().getBytes("UTF-8")
            exchange.sendResponseHeaders(200, bytes.length)
            exchange.responseBody.withStream { it.write(bytes) }
        } catch (e) {
            e.printStackTrace()

            def message = "Server Error = ${e}"
            def bytes = message.getBytes("UTF-8")
            exchange.sendResponseHeaders(500, bytes.length)
            exchange.responseBody.withStream { it.write(bytes) }
        }
    }
}

server = HttpServer.create(new InetSocketAddress(8080), 0)
server.createContext("/", new SimpleHttpHandler())
server.start()

println("LightHttpd Startup. ${new Date()}")