Upgrade to Pro — share decks privately, control downloads, hide ads and more …

JavaでHTTPアクセスしてみる / HTTP access with Java

JavaでHTTPアクセスしてみる / HTTP access with Java

2020/4/30 JJUGナイトセミナー「みんなの小噺」での登壇資料です

Naoki Kishida

April 30, 2020
Tweet

More Decks by Naoki Kishida

Other Decks in Programming

Transcript

  1. JavaでHTTPアクセス
    してみる
    2020/4/30 JJUGナイトセミナー「みんなの小噺」
    LINE Fukuoka きしだ なおき

    View Slide

  2. 自己紹介
    • きしだ なおき
    • @kis
    • https://nowokay.hatenablog.com/
    • LINE Fukuoka
    • 都道府県別の感染者状況が一覧
    できるサイトつくってます
    • https://kishida.github.io/covid19/

    View Slide

  3. おうち生活どうですか?
    • 家にいる
    • 外出しない
    • 人にあわない
    • 寂しい

    View Slide

  4. HTTPアクセスが必要

    View Slide

  5. HTTPアクセス
    • Twitter
    • ブログ
    • ニュース
    • メール
    • チャット
    • ・・・

    View Slide

  6. HTTPアクセスの歴史
    • HTTP(1991)
    • Hyper Text Transfer Protocolプロトコル・・・
    • HTML(1993)
    • マークアップテキスト
    • SOAP(1998)
    • XMLをベースにHTTPで通信を行うRPC
    • Ajax(2005)
    • ブラウザでJavaScriptからXMLをやりとりしてインタラクティブなWebアプリケーションを実装
    • アプリケーションぜんぶWebでいいんでは?
    • JSON(2006)
    • JavaScriptオブジェクトの表現形式をデータ記述に使う
    • RESTful(2009?)
    • RESTでサーバー間通信するといいのでは
    • 特別なRPC定義をせずHTTPのメソッドを利用する
    • サーバー間通信もHTTPでいいのでは

    View Slide

  7. すべての通信はHTTPでいいのでは

    View Slide

  8. JavaでHTTPアクセス
    • 標準APIを使う
    • HTTP Client API
    • Since Java 11 as Standard
    • 外部ライブラリを使う
    • Retrofit
    • https://square.github.io/retrofit/

    View Slide

  9. 標準API

    View Slide

  10. java.net.Socket

    View Slide

  11. java.net.Socket
    • TCP接続を行う
    • バークレイ ソケットをラップ
    • 自分でプロトコルを実装する必要がある

    View Slide

  12. HTTPプロトコル
    • TCP通信を行う
    • HTTPは一往復のプロトコル
    • リクエスト & レスポンス
    • 送信
    • “%s %s HTTP/%s”.formatted(method, resource, version)
    • “Host: %s”.formatted(hostname)
    • Request Headers
    • 空行
    • 受信
    • “HTTP/%s %d %s”.formatted(version, statusCode, statusMessage)
    • Response Headers
    • 空行
    • body

    View Slide

  13. Socket API
    try (var soc = new Socket("example.com", 80);
    var pw = new PrintWriter(soc.getOutputStream());
    var isr = new InputStreamReader(soc.getInputStream());
    var bur = new BufferedReader(isr)) {
    pw.println("GET / HTTP/1.1");
    pw.println("Host: example.com");
    pw.println();
    pw.flush();
    bur.lines()
    .dropWhile(not(String::isEmpty))
    .skip(1)
    .limit(10)
    .forEach(System.out::println);
    }

    View Slide

  14. Socket API
    try (var soc = new Socket("example.com", 80);
    var pw = new PrintWriter(soc.getOutputStream());
    var isr = new InputStreamReader(soc.getInputStream());
    var bur = new BufferedReader(isr)) {
    pw.println("GET / HTTP/1.1");
    pw.println("Host: example.com");
    pw.println();
    pw.flush();
    bur.lines()
    .dropWhile(not(String::isEmpty))
    .skip(1)
    .limit(10)
    .forEach(System.out::println);
    }

    View Slide

  15. Socket API
    try (var soc = new Socket("example.com", 80);
    var pw = new PrintWriter(soc.getOutputStream());
    var isr = new InputStreamReader(soc.getInputStream());
    var bur = new BufferedReader(isr)) {
    pw.println("GET / HTTP/1.1");
    pw.println("Host: example.com");
    pw.println();
    pw.flush();
    bur.lines()
    .dropWhile(not(String::isEmpty))
    .skip(1)
    .limit(10)
    .forEach(System.out::println);
    }

    View Slide

  16. Socket API
    try (var soc = new Socket("example.com", 80);
    var pw = new PrintWriter(soc.getOutputStream());
    var isr = new InputStreamReader(soc.getInputStream());
    var bur = new BufferedReader(isr)) {
    pw.println("GET / HTTP/1.1");
    pw.println("Host: example.com");
    pw.println();
    pw.flush();
    bur.lines()
    .dropWhile(not(String::isEmpty))
    .skip(1)
    .limit(10)
    .forEach(System.out::println);
    }

    View Slide

  17. いやプロトコルの面倒はみたくない

    View Slide

  18. java.net.URLConnection

    View Slide

  19. URLConnection
    var url = "http://example.com";
    URLConnection conn = new URL(url).openConnection();
    System.out.println(((HttpURLConnection)conn).getResponseCode());
    System.out.println(conn.getContentType());
    System.out.println(conn.getHeaderField("Cache-Control"));
    try (var is = conn.getInputStream();
    var isr = new InputStreamReader(is, "utf-8");
    var bur = new BufferedReader(isr)) {
    bur.lines()
    .limit(10)
    .forEach(System.out::println);
    }

    View Slide

  20. URLConnection
    var url = "http://example.com";
    URLConnection conn = new URL(url).openConnection();
    System.out.println(((HttpURLConnection)conn).getResponseCode());
    System.out.println(conn.getContentType());
    System.out.println(conn.getHeaderField("Cache-Control"));
    try (var is = conn.getInputStream();
    var isr = new InputStreamReader(is, "utf-8");
    var bur = new BufferedReader(isr)) {
    bur.lines()
    .limit(10)
    .forEach(System.out::println);
    }

    View Slide

  21. URLConnection
    var url = "http://example.com";
    URLConnection conn = new URL(url).openConnection();
    System.out.println(((HttpURLConnection)conn).getResponseCode());
    System.out.println(conn.getContentType());
    System.out.println(conn.getHeaderField("Cache-Control"));
    try (var is = conn.getInputStream();
    var isr = new InputStreamReader(is, "utf-8");
    var bur = new BufferedReader(isr)) {
    bur.lines()
    .limit(10)
    .forEach(System.out::println);
    }

    View Slide

  22. URLConnection
    var url = "http://example.com";
    URLConnection conn = new URL(url).openConnection();
    System.out.println(((HttpURLConnection)conn).getResponseCode());
    System.out.println(conn.getContentType());
    System.out.println(conn.getHeaderField("Cache-Control"));
    try (var is = conn.getInputStream();
    var isr = new InputStreamReader(is, "utf-8");
    var bur = new BufferedReader(isr)) {
    bur.lines()
    .limit(10)
    .forEach(System.out::println);
    }

    View Slide

  23. URLConnection
    var url = "http://example.com";
    URLConnection conn = new URL(url).openConnection();
    System.out.println(((HttpURLConnection)conn).getResponseCode());
    System.out.println(conn.getContentType());
    System.out.println(conn.getHeaderField("Cache-Control"));
    try (var is = conn.getInputStream();
    var isr = new InputStreamReader(is, "utf-8");
    var bur = new BufferedReader(isr)) {
    bur.lines()
    .limit(10)
    .forEach(System.out::println);
    }

    View Slide

  24. URLConnection
    • URL指定をして通信する様々なプロトコルに対応
    • 結局HTTPしかなかった
    • 細かく使うにはHttpURLConnectionにキャストが必要
    • 主にドキュメントの取得が目的
    • POSTのパラメータ送信はめんどい
    • 接続とリクエストとレスポンスがごっちゃ
    • 同期処理しか行えない
    • 通信待ちでスレッドが止まる

    View Slide

  25. URLConnection使えなくない?

    View Slide

  26. java.net.http.HttpClient

    View Slide

  27. HTTP Client API
    • URLConnectionは使いにくい
    • というか使えない
    • Commons HttpClientやOkHttpを使うようになっていた
    • しかし標準APIとしてHTTP接続は必要では
    • Java 9でIncubator Module
    • Java 11でStandard
    • なのでもうみんな使ってるはず

    View Slide

  28. HTTP Client API
    HttpClient client = HttpClient.newHttpClient();
    var url = "http://example.com";
    HttpRequest req = HttpRequest.newBuilder(URI.create(url)).GET().build();
    HttpResponse> res =
    client.send(req, HttpResponse.BodyHandlers.ofLines());
    System.out.println(res.statusCode());
    // 各ヘッダーを取得する便利メソッドはない
    System.out.println(
    res.headers().firstValue("Content-Type").orElse(""));
    System.out.println(
    res.headers().firstValueAsLong("Content-Length").orElse(-1));
    res.body()
    .limit(10)
    .forEach(System.out::println);

    View Slide

  29. HTTP Client API
    HttpClient client = HttpClient.newHttpClient();
    var url = "http://example.com";
    HttpRequest req = HttpRequest.newBuilder(URI.create(url)).GET().build();
    HttpResponse> res =
    client.send(req, HttpResponse.BodyHandlers.ofLines());
    System.out.println(res.statusCode());
    // 各ヘッダーを取得する便利メソッドはない
    System.out.println(
    res.headers().firstValue("Content-Type").orElse(""));
    System.out.println(
    res.headers().firstValueAsLong("Content-Length").orElse(-1));
    res.body()
    .limit(10)
    .forEach(System.out::println);

    View Slide

  30. HTTP Client API
    HttpClient client = HttpClient.newHttpClient();
    var url = "http://example.com";
    HttpRequest req = HttpRequest.newBuilder(URI.create(url)).GET().build();
    HttpResponse> res =
    client.send(req, HttpResponse.BodyHandlers.ofLines());
    System.out.println(res.statusCode());
    // 各ヘッダーを取得する便利メソッドはない
    System.out.println(
    res.headers().firstValue("Content-Type").orElse(""));
    System.out.println(
    res.headers().firstValueAsLong("Content-Length").orElse(-1));
    res.body()
    .limit(10)
    .forEach(System.out::println);

    View Slide

  31. HTTP Client API
    HttpClient client = HttpClient.newHttpClient();
    var url = "http://example.com";
    HttpRequest req = HttpRequest.newBuilder(URI.create(url)).GET().build();
    HttpResponse> res =
    client.send(req, HttpResponse.BodyHandlers.ofLines());
    System.out.println(res.statusCode());
    // 各ヘッダーを取得する便利メソッドはない
    System.out.println(
    res.headers().firstValue("Content-Type").orElse(""));
    System.out.println(
    res.headers().firstValueAsLong("Content-Length").orElse(-1));
    res.body()
    .limit(10)
    .forEach(System.out::println);

    View Slide

  32. HTTP Client API
    HttpClient client = HttpClient.newHttpClient();
    var url = "http://example.com";
    HttpRequest req = HttpRequest.newBuilder(URI.create(url)).GET().build();
    HttpResponse> res =
    client.send(req, HttpResponse.BodyHandlers.ofLines());
    System.out.println(res.statusCode());
    // 各ヘッダーを取得する便利メソッドはない
    System.out.println(
    res.headers().firstValue("Content-Type").orElse(""));
    System.out.println(
    res.headers().firstValueAsLong("Content-Length").orElse(-1));
    res.body()
    .limit(10)
    .forEach(System.out::println);

    View Slide

  33. HTTP Client API
    • 接続、リクエスト、レスポンスの分離
    • HTTP2対応
    • 非同期対応
    • Java8 APIに対応

    View Slide

  34. ところでHTTP接続にそこまで興味ない

    View Slide

  35. やりたいことはREST APIの呼び出しでは

    View Slide

  36. Retrofit
    • Type safe HTTP Client
    • HTTPをJavaのinterfaceメソッドにマッピングする
    • HTTP接続にはOkHttpを使う
    • https://square.github.io/retrofit/

    View Slide

  37. 例:AEDオープンデータプラットフォーム
    • AEDがどこにあるか検索できる
    • http://hatsunejournal.jp/w8/AEDOpendata/

    View Slide

  38. 例:AED検索
    • 緯度経度を指定して範囲内のAEDを検索
    [
    {
    "DIST": 222,
    "Id": 182,
    "LocationName": "丹南健康福祉センター",
    "Perfecture": "福井県",
    "City": "鯖江市",
    "AddressArea": "水落町1-2-25",
    "Latitude": 35.959898,

    View Slide

  39. レスポンス用オブジェクト
    • レスポンスのJSONをマッピングする
    class AedData {
    public int DIST;
    public String Id;
    public String LocationName;
    public String Perfecture;
    public String City;
    double Latitude;
    double Longitude;
    }

    View Slide

  40. サービスをマッピングする
    • interfaceのメソッドとして定義
    public interface AedService {
    @GET("AEDSearch")
    Call> aedSearch(
    @Query("lat") double lat, @Query("lng") double lng, @Query("r")int r);
    }

    View Slide

  41. AEDの情報を取得
    • 特定のAEDの情報を取得する
    • パラメータをURLに埋め込む
    @GET("id/{id}/")
    Call> aedInfo(@Path("id") String id);

    View Slide

  42. RetrofitでのHTTP接続
    var mapper = new ObjectMapper()
    .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://aed.azure-mobile.net/api/")
    .addConverterFactory(JacksonConverterFactory.create(mapper))
    .build();
    AedService aedService = retrofit.create(AedService.class);
    Call> aedsReq = aedService.aedSearch(33.58, 130.427, 200);
    System.out.println(aedsReq.request().url());
    Response> aedsRes = aedsReq.execute();
    System.out.println(aedsRes.code());
    List aeds = aedsRes.body();
    aeds.forEach(
    aed -> System.out.printf("%s: %s in %dm%n", aed.Id, aed.LocationName, aed.DIST));
    Call> aedReq = aedService.aedInfo("63825");
    List aed = aedReq.execute().body();
    System.out.println(aed.get(0).LocationName);

    View Slide

  43. RetrofitでのHTTP接続
    var mapper = new ObjectMapper()
    .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://aed.azure-mobile.net/api/")
    .addConverterFactory(JacksonConverterFactory.create(mapper))
    .build();
    AedService aedService = retrofit.create(AedService.class);
    Call> aedsReq = aedService.aedSearch(33.58, 130.427, 200);
    System.out.println(aedsReq.request().url());
    Response> aedsRes = aedsReq.execute();
    System.out.println(aedsRes.code());
    List aeds = aedsRes.body();
    aeds.forEach(
    aed -> System.out.printf("%s: %s in %dm%n", aed.Id, aed.LocationName, aed.DIST));
    Call> aedReq = aedService.aedInfo("63825");
    List aed = aedReq.execute().body();
    System.out.println(aed.get(0).LocationName);

    View Slide

  44. RetrofitでのHTTP接続
    var mapper = new ObjectMapper()
    .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://aed.azure-mobile.net/api/")
    .addConverterFactory(JacksonConverterFactory.create(mapper))
    .build();
    AedService aedService = retrofit.create(AedService.class);
    Call> aedsReq = aedService.aedSearch(33.58, 130.427, 200);
    System.out.println(aedsReq.request().url());
    Response> aedsRes = aedsReq.execute();
    System.out.println(aedsRes.code());
    List aeds = aedsRes.body();
    aeds.forEach(
    aed -> System.out.printf("%s: %s in %dm%n", aed.Id, aed.LocationName, aed.DIST));
    Call> aedReq = aedService.aedInfo("63825");
    List aed = aedReq.execute().body();
    System.out.println(aed.get(0).LocationName);

    View Slide

  45. RetrofitでのHTTP接続
    var mapper = new ObjectMapper()
    .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://aed.azure-mobile.net/api/")
    .addConverterFactory(JacksonConverterFactory.create(mapper))
    .build();
    AedService aedService = retrofit.create(AedService.class);
    Call> aedsReq = aedService.aedSearch(33.58, 130.427, 200);
    System.out.println(aedsReq.request().url());
    Response> aedsRes = aedsReq.execute();
    System.out.println(aedsRes.code());
    List aeds = aedsRes.body();
    aeds.forEach(
    aed -> System.out.printf("%s: %s in %dm%n", aed.Id, aed.LocationName, aed.DIST));
    Call> aedReq = aedService.aedInfo("63825");
    List aed = aedReq.execute().body();
    System.out.println(aed.get(0).LocationName);

    View Slide

  46. RetrofitでのHTTP接続
    var mapper = new ObjectMapper()
    .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://aed.azure-mobile.net/api/")
    .addConverterFactory(JacksonConverterFactory.create(mapper))
    .build();
    AedService aedService = retrofit.create(AedService.class);
    Call> aedsReq = aedService.aedSearch(33.58, 130.427, 200);
    System.out.println(aedsReq.request().url());
    Response> aedsRes = aedsReq.execute();
    System.out.println(aedsRes.code());
    List aeds = aedsRes.body();
    aeds.forEach(
    aed -> System.out.printf("%s: %s in %dm%n", aed.Id, aed.LocationName, aed.DIST));
    Call> aedReq = aedService.aedInfo("63825");
    List aed = aedReq.execute().body();
    System.out.println(aed.get(0).LocationName);

    View Slide

  47. RetrofitでのHTTP接続
    var mapper = new ObjectMapper()
    .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://aed.azure-mobile.net/api/")
    .addConverterFactory(JacksonConverterFactory.create(mapper))
    .build();
    AedService aedService = retrofit.create(AedService.class);
    Call> aedsReq = aedService.aedSearch(33.58, 130.427, 200);
    System.out.println(aedsReq.request().url());
    Response> aedsRes = aedsReq.execute();
    System.out.println(aedsRes.code());
    List aeds = aedsRes.body();
    aeds.forEach(
    aed -> System.out.printf("%s: %s in %dm%n", aed.Id, aed.LocationName, aed.DIST));
    Call> aedReq = aedService.aedInfo("63825");
    List aed = aedReq.execute().body();
    System.out.println(aed.get(0).LocationName);

    View Slide

  48. RetrofitでのHTTP接続
    var mapper = new ObjectMapper()
    .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
    Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://aed.azure-mobile.net/api/")
    .addConverterFactory(JacksonConverterFactory.create(mapper))
    .build();
    AedService aedService = retrofit.create(AedService.class);
    Call> aedsReq = aedService.aedSearch(33.58, 130.427, 200);
    System.out.println(aedsReq.request().url());
    Response> aedsRes = aedsReq.execute();
    System.out.println(aedsRes.code());
    List aeds = aedsRes.body();
    aeds.forEach(
    aed -> System.out.printf("%s: %s in %dm%n", aed.Id, aed.LocationName, aed.DIST));
    Call> aedReq = aedService.aedInfo("63825");
    List aed = aedReq.execute().body();
    System.out.println(aed.get(0).LocationName);

    View Slide

  49. HTTP以外のプロトコル
    • HTTPS(2000)
    • HTTP over SSL/TLS
    • ブラウザとサーバーの通信を暗号化
    • サーバーがすり替えられてないことを確認したり通信の盗聴を防ぐ
    • WebSocket(2011)
    • ブラウザとサーバーで双方向通信を行う
    • 最初の接続はHTTPで行う
    • WebRTC(2011)
    • リアルタイム通信
    • UDP
    • P2P
    • HTTP/2 (2015)
    • HTTPを拡張
    • 効率化、複数のリクエストに対応
    • HTTP/3
    • HTTP over QUIC
    • UDP

    View Slide

  50. まとめ
    • HTTP接続は大事
    • いろいろ便利なライブラリがある
    • サーバー間もHTTP通信
    • 疎結合
    • つまり疎結合にはHTTP通信
    • ソーシャルディスタンスをとるにはHTTP接続が不可欠
    • 早く外に出れるよう引きこもりがんばりましょう

    View Slide