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

ある日「Webエンジニアなら、Webサーバーは作れますよね」と言われたら? ~ 3つのJVM言語で作って学ぶ

ある日「Webエンジニアなら、Webサーバーは作れますよね」と言われたら? ~ 3つのJVM言語で作って学ぶ

技育CAMP 2021-05-20 の発表資料です。

Shunsuke Tadokoro

May 20, 2021
Tweet

More Decks by Shunsuke Tadokoro

Other Decks in Technology

Transcript

  1. ͓࿩͢͠Δ͜ͱ w 8FCαʔόʔͬͯԿͯ͠Δͷʁ w 4DBMB $MPKVSFʹ͍ͭͯ w 8FCαʔόʔΛ࡞Γͳ͕Βͭͷ+7.ݴޠΛֶ΅͏ w ͬ͘͟ΓΞʔΩςΫνϟ

    w ֤ݴޠͰ࣮૷ͯ͠ΈΔ w 4PDLFUͷѻ͍ w ਖ਼نදݱ w จࣈྻͷѻ͍ w Ϧιʔεͷ؅ཧ w ฒྻॲཧ w 8FCαʔόʔΛ࡞ͬͯɺԿΛಘͨʁ
  2. 4DBMB w +BWBͱͷ૬ޓӡ༻ੑ w γʔϜϨεͳݺͼग़͠ɺ+BWBඪ४ϥΠϒϥϦͷ࠶ར༻ w ؆ܿੑ w লུՄೳͳߏจɺܕਪ࿦ɺڧྗͳඪ४ϥΠϒϥϦ w

    ந৅౓ͷߴ͍ίʔυɺ৽੍͍͠ޚߏจΛఆٛͰ͖Δදݱྗ w 8IBUͷڧௐɺ)PXͷӅณ w ੩తܕ෇͚ w ݕূՄೳੑɺϦϑΝΫλͷ͠΍͢͞ɺυΩϡϝϯτੑ
  3. 4DBMBܕͷදݱྗ // `@`͔Β࢝·ͬͯ3จࣈҎ্16จࣈҎ಺ɺ@admin΍@Admin͸ڐ༰͠ͳ͍ type UserIdRule = StartsWith["@"] And MinSize[3] And

    MaxSize[16] And Not[MatchesRegex["(?i)@admin"]] val userId1: String Refined UserIdRule = "@todokr" // ҎԼ͸ίϯύΠϧΤϥʔ val userId2: String Refined UserIdRule = "@admin" val userId3: String Refined UserIdRule = "todokr" val userId4: String Refined UserIdRule = "@uryyyyyyyyyyyyyy"
  4. $MPKVSF w -JTQ w จ๏͕গͳ͍ɺσʔλͱͯ͠ͷίʔυ w ؔ਺ܕϓϩάϥϛϯάͷͨΊͷݴޠ w ୈҰڃؔ਺ɺΠϛϡʔλϒϧͳσʔλߏ଄ɺ࠶ؼతͳϧʔϓ w

    +BWBͱͷ૬ޓӡ༻ੑ w ฒߦॲཧͷͨΊʹઃܭ w 3&1-Λ׆͔ͨ͠ΠϯλϥΫςΟϒΠϯΫϦϝϯλϧͳ։ൃ
  5. ࢛ଇԋࢉ 12 + 40 10 - 1 2 * 3

    5 / 2 +BWB 4DBMB (+ 12 40) (- 10 1) (* 2 3) (/ 5 2) ; -> 5/2 ෼਺Λѻ͏Ratioܕ $MPKVSF
  6. ม਺એݴ int x = 10; +BWB val x = 10

    val x: Int = 10 4DBMB (def x 10) (let [y 10] (+ y 3)) ; y͸letͷׅހ಺͚ͩͰࢀরͰ͖Δ $MPKVSF
  7. ϝιουɾؔ਺એݴ public int f(int x) { return x + 1;

    } +BWB (defn f [x] (+ x 1)) $MPKVSF def f(x: Int) = x + 1 4DBMB
  8. ࠓճ༻ҙ࣮ͨ͠૷ w ىಈ͠ɺMPDBMIPTUͷಛఆͷϙʔτͰ)551ϦΫΤετΛ଴ͪड͚Δ w ରԠ͢Δ)551ϦΫΤετϝιου͸(&5ͷΈ
 ͦΕҎ֎ͷϝιου΋(&5ͱΈͳ͢ w QVCMJDσΟϨΫτϦΑΓ্ͷ֊૚΁ͷϦΫΤετʹ͸
 'PSCJEEFOΛฦ͢ w

    Ϧιʔεͷ.*.&͸֎෦ϑΝΠϧͰઃఆͰ͖Δ w ϦΫΤετΛϒϩοΫ͠ͳ͍ʢϚϧνεϨουʣ w ,FFQ"MJWF͸͠ͳ͍ɻίωΫγϣϯ͸౎౓DMPTF͢Δ w )551$BDIF͸͠ͳ͍ɻ͸ฦ͞ͳ͍
  9. 4PDLFUͷѻ͍ // αʔόʔιέοτͷੜ੒ ServerSocket serverSocket = new ServerSocket(8080); while (true)

    { // ઀ଓΛ଴ͪड͚Δɻ઀ଓ͞ΕΔ·ͰϒϩοΫɻ Socket socket = serverSocket.accept(); InputStream in = socket.getInputStream(); OutputStream out = socket.getOutputStream(); ... } ιέοτͷੜ੒ͱ઀ଓͷ଴ͪड͚
  10. 4PDLFUͷѻ͍ // αʔόʔιέοτͷੜ੒ val serverSocket = new ServerSocket(8080) while (true)

    { // ઀ଓΛ଴ͪड͚Δɻ઀ଓ͞ΕΔ·ͰϒϩοΫ val socket = serverSocket.accept val in = s.getInputStream val out = s.getOutputStream ... } ιέοτͷੜ੒ͱ઀ଓͷ଴ͪड͚
  11. 4PDLFUͷѻ͍ (let [server-socket (new ServerSocket 8080)] (while true (let [socket

    (.accept server-socket) in (.getInputStream socket) out (.getOutputStream socket)] ...))) ιέοτͷੜ੒ͱ઀ଓͷ଴ͪड͚
  12. ໊લ෇͖άϧʔϓ w ਖ਼نදݱͷάϧʔϓʹ໊લΛ෇͚Δ͜ͱ͕Ͱ͖Δ w άϧʔϓͷॱং͕มΘͬͨͱͯ͠΋औΓग़͢ॲཧ͸ͦͷ··Ͱྑ͍ import java.util.regex.*; String regex =

    "(?<year>\\d+)/(?<month>\\d+)/(?<day>\\d+)"; Pattern p = Pattern.compile(regex); Matcher m = p.matcher("2017/11/18"); if (m.find()) { System.out.println(m.group("year")); // 2017 System.out.println(m.group("month")); // 11 System.out.println(m.group("day")); // 18 }
  13. public static Pattern requestLinePattern = Pattern.compile("(?<method>.*) (?<path>.*?) (?<version>.*?)"); public Request

    fromInputStream(InputStream in){ BufferedReader reader = new BufferedReader(new InputStreamReader(in)); String requestLine = reader.readLine(); Matcher matcher = requestLinePattern.matcher(requestLine); if (!matcher.find()) return null; String method = matcher.group("method"); String targetPath = matcher.group("path"); String httpVersion = matcher.group("version"); return new Request(method, targetPath, httpVersion); } ໊લ෇͖άϧʔϓ ϦΫΤετϥΠϯͷ֤ཁૉʹ໊લΛ෇͚ͯநग़
  14. ύλʔϯϚον w +BWBͷTXJUDIจʹࣅ͍ͯΔ͕ɺΑΓॊೈͰڧྗ w ஋ʹҰக͢Δ͔͚ͩͰͳ͘ɺܕ΍ߏ଄Ͱ෼ذͤ͞Δ͜ͱ͕Ͱ͖Δ 0 match { case 0

    => "Zero" // "Zero" case _ => "Other" } List(1, 2, 3) match { case List(_, x, _) => x // 2 case _ => -1 } 1 match { x: Int => s"$x͸Int" // 1͸Int x: String => s"$x͸String" x => s"$x͸???" }
  15. ύλʔϯϚον val Pattern = "(.+) (.+) (.+)".r requestLine match {

    case Pattern(method, path, version) => Some(Request(method, path, version)) case _ => None } 3FRVFTU1BSTFSTDBMBʢൈਮҰ෦վมʣ
  16. 0QUJPOܕ requestLine match { case pattern(method, path, version) => Some(Request(method,

    path, version)) case _ => None } w +BWBͰ͍͏0QUJPOBMʢΈ͍ͨͳ΋ͷʣ w )BTLFMMͰ͍͏.BZCF w 4PNFͱ/POF͔ΒͳΔܕʢ୅਺తσʔλܕʣ w ஋͕͋Δ͔ͳ͍͔෼͔Βͳ͍ঢ়ଶΛද͢ w ஋͕ଘࡏ͢Δ͔ͷνΣοΫΛڧ੍Ͱ͖Δ
  17. SFpOE w ਖ਼نදݱʹϚονͨ͠จࣈྻΛऔಘ w Ҿ਺ʹάϧʔϓԽͨ͠ਖ਼نදݱΦϒδΣΫτΛ౉͢ͱɺ
 ઌ಄ʹ͸Ϛονͨ͠จࣈྻશମɺҎ߱ʹΩϟϓνϟ͞ΕͨจࣈྻͷϕΫλʔ ;; #"" ͸java.util.regex.PatternͷϦςϥϧ (re-find

    #"(.+)/(.+)/(.+)" "2017/11/18") ;; => ["2017/11/18" "2017" "11" "18"] ;; restͰઌ಄Ҏ֎ͷཁૉΛऔಘ (rest (re-find #"(.+)/(.+)/(.+)" "2017/11/18")) ;; => ("2017" "11" "18")
  18. SFpOE [JQNBQ (let [line "GET / HTTP/1.1"] (zipmap [:method :path

    :version] (rest (re-find #"(.+) (.+) (.+)" line)))) ;; => {:method "GET", :path "/", :version "HTTP/1.1"} ϦΫΤετϥΠϯͷ֤ཁૉΛ.BQʹม׵
  19. SFpOE [JQNBQ <NFUIPEQBUIWFSTJPO> <(&5)551> (let [line "GET / HTTP/1.1"] (zipmap

    [:method :path :version] (rest (re-find #"(.+) (.+) (.+)" line)))) ;; => {:method "GET", :path "/", :version "HTTP/1.1"} ϦΫΤετϥΠϯͷ֤ཁૉΛ.BQʹม׵
  20. SFpOE [JQNBQ <NFUIPEQBUIWFSTJPO> <(&5)551> (let [line "GET / HTTP/1.1"] (zipmap

    [:method :path :version] (rest (re-find #"(.+) (.+) (.+)" line)))) ;; => {:method "GET", :path "/", :version "HTTP/1.1"} ϦΫΤετϥΠϯͷ֤ཁૉΛ.BQʹม׵
  21. SFpOE [JQNBQ <NFUIPEQBUIWFSTJPO> <(&5)551> (let [line "GET / HTTP/1.1"] (zipmap

    [:method :path :version] (rest (re-find #"(.+) (.+) (.+)" line)))) ;; => {:method "GET", :path "/", :version "HTTP/1.1"} ϦΫΤετϥΠϯͷ֤ཁૉΛ.BQʹม׵
  22. จࣈྻ݁߹ w ϨεϙϯεʢΦϒδΣΫτϚοϓʣ͔Β
 )551ϨεϙϯεϔομΛ૊Έཱ͍ͯͨ public class Response { public final

    Status status; public final String contentType; public final int contentLength; public final byte[] body; }
  23. 4USJOH#VJMEFS String response = "HTTP/1.1 " + status.statusCode + CRLF

    + "Server: SimpleJavaHttpServer" + CRLF + "Content-Type: " + contentType + CRLF + "Content-Length: " + 
 String.valueOf(contentLength) + CRLF + "Connection: Close" + CRLF + CRLF; 3FTQPOTFΦϒδΣΫτΛ)551Ϩεϙϯε΁ม׵
  24. 5SJQMF2VPUF "Hello triple quote!\nHello stripMargin!" """Hello triple quote! Hello stripMargin!"""

    """|Hello triple quote! |Hello stripMargin!""".stripMargin w վߦΛؚΉจࣈྻΛຒΊࠐΉʹ͸ɺΛ࢖͏ w ΠϯσϯτΛଗ͑Δʹ͸Πϯσϯτจࣈ c ͱTUSJQ.BSHJOΛ࢖͏
  25. 4USJOH*OUFSQPMBUJPOͱ5SJQMF2VPUF val response = s"""HTTP/1.1 ${status.value} |Date: ${rfc1123Formatter.format(now)} |Server: SimpleScalaHttpServer

    |Content-Type: $contentType |Content-Length: ${body.length.toString} |Connection: Close | |""".stripMargin 3FTQPOTFΦϒδΣΫτΛ)551Ϩεϙϯε΁ม׵
  26. TUS

  27. w $MPKVSFͰ͸จࣈྻͷ݁߹͸ Ͱ͸ͳ͘TUS w Մม௕Ҿ਺ɺ4ࣜͳΒͰ͸ TUS (+ "hoge" "fuga") ClassCastException

    java.lang.String cannot be cast to java.lang.Number clojure.lang.Numbers.add (Numbers.java:128) (str "hoge" "fuga" "piyo") // => hogefugapiyo
  28. TUS (let [header (str "HTTP/1.1" SP status SP reason-phrase CRLF

    "Content-Length:" (count body) CRLF "Content-Type:" content-type CRLF "Connection: Close" CRLF CRLF)] ...) 3FTQPOTFΦϒδΣΫτΛ)551Ϩεϙϯε΁ม׵
  29. 5SZXJUI3FTPVSDFT try (InputStream in = new FileInputStream(file)) { // Կ͔ϦιʔεΛѻ͏ॲཧ

    } // try۟Λൈ͚ͨΒclose w USZ۟Λൈ͚ͨΒࣗಈͰDMPTF w ର৅͸KBWBJP$MPTFBCMF KBWBMBOH"VUP$MPTFBCMFͷ࣮૷Ϋϥε
  30. -PBO1BUUFSO w ʮआΓͨΒฦ͢ʯΛ࣮֬ʹߦ͏ͨΊͷΠσΟΦϜ w ͔͋ͨ΋ݴޠ੍͕࣋ͭޚߏ଄ͷΑ͏ͳϝιουΛɺ
 ϓϩάϥϚ͕؆୯ʹఆٛͰ͖Δͷ΋4DBMBͷΑ͍ͱ͜Ζ val reader = new

    BufferedReader(...) using(reader) { r => // readerΛ࢖ͬͨԿ͔ͷॲཧ } // ϒϩοΫΛൈ͚ͨΒreader͸close͞Ε͍ͯΔ ˞4DBMB͔Βඪ४ϥΠϒϥϦʹˢͱ΄΅ಉ౳ͷTDBMBVUJM6TJOH͕௥Ճ͞Ε·͕ͨ͠ɺ
 ΠϝʔδΛ௫ΉͨΊʹࣗ෼Ͱ࣮૷ͯ͠Έ·͢
  31. -PBO1BUUFSOͷ࣮૷ def using[A, R <: Closeable](resource: R)(f: R => A):

    A = { try { f(resource) } finally { resource.close() } }
  32. -PBO1BUUFSOͷ࣮૷ def using[A, R <: Closeable](resource: R)(f: R => A):

    A = { try { f(resource) } finally { resource.close() } } ܕม਺3͸$MPTFBCMF͔ͦͷαϒλΠϓɻDMPTFϝιουΛ࣋ͭ͜ͱΛอূɻ
  33. -PBO1BUUFSOͷ࣮૷ def using[A, R <: Closeable](resource: R)(f: R => A):

    A = { try { f(resource) } finally { resource.close() } } 3ܕͷԿ͔Λड͚औΓɺԿΒ͔ͷܕ"Λฦؔ͢਺ΛҾ਺ʹͱΔ
  34. -PBO1BUUFSOͷ࣮૷ def using[A, R <: Closeable](resource: R)(f: R => A):

    A = { try { f(resource) } finally { resource.close() } } Ϧιʔεʹରͯؔ͠਺Λద༻
  35. -PBO1BUUFSOͷ࣮૷ def using[A, R <: Closeable](resource: R)(f: R => A):

    A = { try { f(resource) } finally { resource.close() } } ࠷ޙʹDMPTF
  36. -PBO1BUUFSO using(socket) { s => val in = s.getInputStream val

    out = s.getOutputStream val request = parser.fromInputStream(in) val response = request.map(handleRequest) response.foreach(_.writeTo(out)) } VTJOHϒϩοΫΛൈ͚ͨΒɺTPDLFUΛDMPTF͢Δ
  37. 5ISFBE class HogeThread extends Thread { public void run() {

    // Կ͔ඇಉظʹ࣮ߦ͍ͨ͠ॲཧ } } HogeThread h = new HogeThread(); h.start(); class FugaRunnable implements Runnable { public void run() { // Կ͔ඇಉظʹ࣮ߦ͍ͨ͠ॲཧ } } FugaRunnable f = new Thread(new FugaRunnable()); f.start(); 5ISFBEΛFYUFOET3VOOBCMFΛJNQMFNFOUT
  38. 5ISFBE public class WorkerThread extends Thread { private Socket socket;

    private RequestParser parser; private RequestHandler handler; public WorkerThread( Socket socket, RequestParser parser, RequestHandler handler) { ... 5ISFBEͷఆٛ 5ISFBEͷىಈ Thread worker = new WorkerThread(socket, parser, handler); worker.start();
  39. &YFDVUPS4FSWJDF ExecutorService cachedPool = Executors.newCachedThreadPool(); cachedPool.execute(runnable); ExecutorService fixedPool = Executors.newFixedThreadPool(4);

    fixedPool.execute(runnable); w ΑΓߴਫ४ͳฒྻॲཧͷͨΊͷ࢓૊Έ w εϨουϓʔϧͷछྨ΋ࢦఆͰ͖Δ
  40. 'VUVSF w 'VUVSFBQQMZ͸౉͞ΕͨॲཧΛඇಉظʹ࣮ߦ w ͍ͭͲͷΑ͏ʹඇಉظʹ࣮ߦ͢Δ͔͸&YFDVUJPO$POUFYU࣍ୈ import scala.concurrent.Future // ForkJoinPoolɺσϑΥϧτͰ͸ίΞ਺෼ͷฒྻ౓Ͱॲཧ
 import

    scala.concurrent.ExecutionContext.Implicits.global val result: Future[User] = Future { // .apply͸লུͰ͖Δ userRepository.fetch(userId) } result.map(user => user.id) result.flatMap(user => tweetRepository.fetch(user.id))
  41. &YFDVUJPO$POUFYU͸Ͳ͏ड͚औΔʁ w 'VUVSFBQQMZʹ͸Ҿ਺ϒϩοΫ͕ͭ w ͭ໨ͷҾ਺ϒϩοΫͰ&YFDVUJPO$POUFYUΛड͚औΔ object Future { ... def

    apply[T](body: => T)(implicit executor: ExecutionContext): Future[T] = unit.map(_ => body) ... } Future { userRepository.fetch(user) } // ↑͸Future#applyͷݺͼग़͠
  42. 8IBUͱ)PXͷ෼཭ w ୈҰҾ਺ϒϩοΫlCPEZ5z w 8IBUʮԿΛ࣮ߦ͢Δ͔ʯ w ୈೋҾ਺ϒϩοΫlJNQMJDJUFYFDVUPS&YFDVUJPO$POUFYUz w )PXʮͲͷΑ͏ʹ࣮ߦ͢Δ͔ʯ w

    JNQMJDJUΩʔϫʔυ͕͍͍ͭͯΔͷͰɺ໌ࣔతʹҾ਺ͱͯ͠౉͢ඞཁͳ͠ w 'VUVSF EP4PNFUIJOH FYFDVUPS object Future { ... def apply[T](body: => T)(implicit executor: ExecutionContext): Future[T] = unit.map(_ => body) ... }
  43. JNQMJDJU8IBUͱ)PXͷ෼཭ w )PX͕ڊେʹͳΔͱɺίʔυͷຊདྷͷҙਤ͕ຒ΋Εͯ͠·͏ w JNQMJDJUͷେ͖ͳϞνϕʔγϣϯ͸)PXͷӅณ w 8IBUͱ)PXΛ෼཭͢Δ͜ͱͰɺ໨తΛ୺తʹࣔ͢͜ͱ͕Ͱ͖Δ val values: Seq[(String,

    Option[Int])] val sorted = sort(values)( tuple2Comparator( stringComparator, optionComparator(intComparator))) val sorted = sort(values) // implicit
  44. $MPKVSFͷฒߦॲཧؔ਺ εϨουϓʔϧ εϨου਺ TFOE 'JYFE5ISFBE1PPM  ίΞ਺ TFOEPGG $BDIFE5ISFBE1PPM ੍ݶͳ͠

    GVUVSFGVUVSFDBMM QNBQQDBMMT $BDIFE5ISFBE1PPM ੍ݶͳ͠ HP 'JYFE5ISFBE1PPM ίΞ਺   UISFBE
 UISFBEDBMM $BDIFE5ISFBE1PPM ੍ݶͳ͠ SFEVDFST 'PSL+PJO1PPM ࣗಈ੍ޚ
  45. *0ό΢ϯυ$16ό΢ϯυ ΞϓϦέʔγϣϯͷʮෛՙʯ͸ɺ
 *0ό΢ϯυͱ$16ό΢ϯυͷछྨʹେผ͞ΕΔ w *0ό΢ϯυ w *0͕ύϑΥʔϚϯεͷϘτϧωοΫ w $16͸ʮ଴ͪʯ͕ଟ͘ͳΔ w

    σΟεΫΞΫηε΍ϦϞʔτ௨৴ͳͲ w$16ό΢ϯυ w $16͕ύϑΥʔϚϯεͷϘτϧωοΫ w $16͸ϑϧՔಇ w େن໛ͳՊֶܭࢉͳͲ
  46. $MPKVSFͷฒߦॲཧؔ਺ εϨουϓʔϧ εϨου਺ TFOE 'JYFE5ISFBE1PPM  ίΞ਺ TFOEPGG $BDIFE5ISFBE1PPM ੍ݶͳ͠

    GVUVSFGVUVSFDBMM QNBQQDBMMT $BDIFE5ISFBE1PPM ੍ݶͳ͠ HP 'JYFE5ISFBE1PPM ίΞ਺   UISFBE
 UISFBEDBMM $BDIFE5ISFBE1PPM ੍ݶͳ͠ SFEVDFST 'PSL+PJO1PPM ࣗಈ੍ޚ
  47. $MPKVSFͷฒߦॲཧؔ਺ εϨουϓʔϧ εϨου਺ TFOE 'JYFE5ISFBE1PPM  ίΞ਺ TFOEPGG $BDIFE5ISFBE1PPM ੍ݶͳ͠

    GVUVSFGVUVSFDBMM QNBQQDBMMT $BDIFE5ISFBE1PPM ੍ݶͳ͠ HP 'JYFE5ISFBE1PPM ίΞ਺   UISFBE
 UISFBEDBMM $BDIFE5ISFBE1PPM ੍ݶͳ͠ SFEVDFST 'PSL+PJO1PPM ࣗಈ੍ޚ
  48. UISFBE (thread (with-open [s socket in (.getInputStream s) out (.getOutputStream

    s)] (-> (request-parser/from-input-stream in) (request-handler/handle-request) (response-writer/write out)))) $BDIFE5ISFBE1PPMΛ࢖ͬͨฒྻॲཧ
  49. ಘͨ΋ͷͪΐͬͱਂ͘જΔ༐ؾ w ʮ࿙Εͷ͋Δந৅Խʯͷ΋ͱͰ͸ɺ໰୊ղܾͷͨΊʹ
 ීஈ৮Δٕज़ͷԼͷϨΠϠʔʹજΔ͜ͱ͕ආ͚ΒΕͳ͍
 ͔ͭɺ๛෋ͳ৘ใ͕͋Δͱ΋ݶΒͳ͍ w 8FCαʔόʔΛ࡞Δ͜ͱͰɺ8FCϑϨʔϜϫʔΫ͕ͲΜͳ࢓ࣄΛ ͍ͯ͠Δ͔΋ͳΜͱͳ͘෼͔ΔΑ͏ʹ w ӳޠͷҰ࣍৘ใʹ౰ͨΔ͕͍ͤͭͨ͘͜ͱͰɺ


    ະ຋༁ͷ৽͍ٕ͠ज़ͷυΩϡϝϯτͳͲ΋ԲͤͣಡΊΔΑ͏ʹ w ʮ࢓ࣄͰ࢖͍ͬͯΔϑϨʔϜϫʔΫ΍ϛυϧ΢ΣΞͷίʔυ΋
 ಡΜͰΈΑ͏ɻ͋ΘΑ͘͹ίϯτϦϏϡʔτͯ͠ΈΑ͏ʯͱ
 ͪΐͬͱਂ͘જͬͯΈΔ༐ؾˍڵຯΛ͖͔͚࣋ͭͬʹ