Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

HTTPアクセスが必要

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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でいいのでは

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

標準API

Slide 10

Slide 10 text

java.net.Socket

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

java.net.URLConnection

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

URLConnection使えなくない?

Slide 26

Slide 26 text

java.net.http.HttpClient

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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