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

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

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

9e840766611f942c7c0a9ad6987a5d78?s=128

Naoki Kishida

April 30, 2020
Tweet

Transcript

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

  2. 自己紹介 • きしだ なおき • @kis • https://nowokay.hatenablog.com/ • LINE

    Fukuoka • 都道府県別の感染者状況が一覧 できるサイトつくってます • https://kishida.github.io/covid19/
  3. おうち生活どうですか? • 家にいる • 外出しない • 人にあわない • 寂しい

  4. HTTPアクセスが必要

  5. HTTPアクセス • Twitter • ブログ • ニュース • メール •

    チャット • ・・・
  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でいいのでは
  7. すべての通信はHTTPでいいのでは

  8. JavaでHTTPアクセス • 標準APIを使う • HTTP Client API • Since Java

    11 as Standard • 外部ライブラリを使う • Retrofit • https://square.github.io/retrofit/
  9. 標準API

  10. java.net.Socket

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

  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
  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); }
  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); }
  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); }
  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); }
  17. いやプロトコルの面倒はみたくない

  18. java.net.URLConnection

  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); }
  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); }
  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); }
  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); }
  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); }
  24. URLConnection • URL指定をして通信する様々なプロトコルに対応 • 結局HTTPしかなかった • 細かく使うにはHttpURLConnectionにキャストが必要 • 主にドキュメントの取得が目的 •

    POSTのパラメータ送信はめんどい • 接続とリクエストとレスポンスがごっちゃ • 同期処理しか行えない • 通信待ちでスレッドが止まる
  25. URLConnection使えなくない?

  26. java.net.http.HttpClient

  27. HTTP Client API • URLConnectionは使いにくい • というか使えない • Commons HttpClientやOkHttpを使うようになっていた

    • しかし標準APIとしてHTTP接続は必要では • Java 9でIncubator Module • Java 11でStandard • なのでもうみんな使ってるはず
  28. HTTP Client API HttpClient client = HttpClient.newHttpClient(); var url =

    "http://example.com"; HttpRequest req = HttpRequest.newBuilder(URI.create(url)).GET().build(); HttpResponse<Stream<String>> 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);
  29. HTTP Client API HttpClient client = HttpClient.newHttpClient(); var url =

    "http://example.com"; HttpRequest req = HttpRequest.newBuilder(URI.create(url)).GET().build(); HttpResponse<Stream<String>> 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);
  30. HTTP Client API HttpClient client = HttpClient.newHttpClient(); var url =

    "http://example.com"; HttpRequest req = HttpRequest.newBuilder(URI.create(url)).GET().build(); HttpResponse<Stream<String>> 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);
  31. HTTP Client API HttpClient client = HttpClient.newHttpClient(); var url =

    "http://example.com"; HttpRequest req = HttpRequest.newBuilder(URI.create(url)).GET().build(); HttpResponse<Stream<String>> 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);
  32. HTTP Client API HttpClient client = HttpClient.newHttpClient(); var url =

    "http://example.com"; HttpRequest req = HttpRequest.newBuilder(URI.create(url)).GET().build(); HttpResponse<Stream<String>> 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);
  33. HTTP Client API • 接続、リクエスト、レスポンスの分離 • HTTP2対応 • 非同期対応 •

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

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

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

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

  38. 例:AED検索 • 緯度経度を指定して範囲内のAEDを検索 [ { "DIST": 222, "Id": 182, "LocationName":

    "丹南健康福祉センター", "Perfecture": "福井県", "City": "鯖江市", "AddressArea": "水落町1-2-25", "Latitude": 35.959898,
  39. レスポンス用オブジェクト • レスポンスのJSONをマッピングする class AedData { public int DIST; public

    String Id; public String LocationName; public String Perfecture; public String City; double Latitude; double Longitude; }
  40. サービスをマッピングする • interfaceのメソッドとして定義 public interface AedService { @GET("AEDSearch") Call<List<AedData>> aedSearch(

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

  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<List<AedData>> aedsReq = aedService.aedSearch(33.58, 130.427, 200); System.out.println(aedsReq.request().url()); Response<List<AedData>> aedsRes = aedsReq.execute(); System.out.println(aedsRes.code()); List<AedData> aeds = aedsRes.body(); aeds.forEach( aed -> System.out.printf("%s: %s in %dm%n", aed.Id, aed.LocationName, aed.DIST)); Call<List<AedData>> aedReq = aedService.aedInfo("63825"); List<AedData> aed = aedReq.execute().body(); System.out.println(aed.get(0).LocationName);
  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<List<AedData>> aedsReq = aedService.aedSearch(33.58, 130.427, 200); System.out.println(aedsReq.request().url()); Response<List<AedData>> aedsRes = aedsReq.execute(); System.out.println(aedsRes.code()); List<AedData> aeds = aedsRes.body(); aeds.forEach( aed -> System.out.printf("%s: %s in %dm%n", aed.Id, aed.LocationName, aed.DIST)); Call<List<AedData>> aedReq = aedService.aedInfo("63825"); List<AedData> aed = aedReq.execute().body(); System.out.println(aed.get(0).LocationName);
  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<List<AedData>> aedsReq = aedService.aedSearch(33.58, 130.427, 200); System.out.println(aedsReq.request().url()); Response<List<AedData>> aedsRes = aedsReq.execute(); System.out.println(aedsRes.code()); List<AedData> aeds = aedsRes.body(); aeds.forEach( aed -> System.out.printf("%s: %s in %dm%n", aed.Id, aed.LocationName, aed.DIST)); Call<List<AedData>> aedReq = aedService.aedInfo("63825"); List<AedData> aed = aedReq.execute().body(); System.out.println(aed.get(0).LocationName);
  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<List<AedData>> aedsReq = aedService.aedSearch(33.58, 130.427, 200); System.out.println(aedsReq.request().url()); Response<List<AedData>> aedsRes = aedsReq.execute(); System.out.println(aedsRes.code()); List<AedData> aeds = aedsRes.body(); aeds.forEach( aed -> System.out.printf("%s: %s in %dm%n", aed.Id, aed.LocationName, aed.DIST)); Call<List<AedData>> aedReq = aedService.aedInfo("63825"); List<AedData> aed = aedReq.execute().body(); System.out.println(aed.get(0).LocationName);
  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<List<AedData>> aedsReq = aedService.aedSearch(33.58, 130.427, 200); System.out.println(aedsReq.request().url()); Response<List<AedData>> aedsRes = aedsReq.execute(); System.out.println(aedsRes.code()); List<AedData> aeds = aedsRes.body(); aeds.forEach( aed -> System.out.printf("%s: %s in %dm%n", aed.Id, aed.LocationName, aed.DIST)); Call<List<AedData>> aedReq = aedService.aedInfo("63825"); List<AedData> aed = aedReq.execute().body(); System.out.println(aed.get(0).LocationName);
  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<List<AedData>> aedsReq = aedService.aedSearch(33.58, 130.427, 200); System.out.println(aedsReq.request().url()); Response<List<AedData>> aedsRes = aedsReq.execute(); System.out.println(aedsRes.code()); List<AedData> aeds = aedsRes.body(); aeds.forEach( aed -> System.out.printf("%s: %s in %dm%n", aed.Id, aed.LocationName, aed.DIST)); Call<List<AedData>> aedReq = aedService.aedInfo("63825"); List<AedData> aed = aedReq.execute().body(); System.out.println(aed.get(0).LocationName);
  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<List<AedData>> aedsReq = aedService.aedSearch(33.58, 130.427, 200); System.out.println(aedsReq.request().url()); Response<List<AedData>> aedsRes = aedsReq.execute(); System.out.println(aedsRes.code()); List<AedData> aeds = aedsRes.body(); aeds.forEach( aed -> System.out.printf("%s: %s in %dm%n", aed.Id, aed.LocationName, aed.DIST)); Call<List<AedData>> aedReq = aedService.aedInfo("63825"); List<AedData> aed = aedReq.execute().body(); System.out.println(aed.get(0).LocationName);
  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
  50. まとめ • HTTP接続は大事 • いろいろ便利なライブラリがある • サーバー間もHTTP通信 • 疎結合 •

    つまり疎結合にはHTTP通信 • ソーシャルディスタンスをとるにはHTTP接続が不可欠 • 早く外に出れるよう引きこもりがんばりましょう