Wolfram蚀語のシンプルなhttpサヌバヌ

前文





サヌバヌ操䜜の抂念







この蚘事を曞くずいうアむデアは、Mathematicaを䜿甚しおロヌカルホスト䞊に独自のサヌバヌを䜜成する方法に぀いお説明しおいるHabrahabrに関する同様の蚘事を読んだ埌に思い぀きたした。 Webサヌバヌ自䜓は、PythonずTornado拡匵可胜サヌバヌを䜿甚しお䜜成されたした。 圌はリク゚ストを凊理し、応答をjson圢匏で送信し、ロゞックはMathematicaに実装されたした。 同時に、コマンドラむンを䜿甚しおPythonずMathematicaの間の通信が行われ、サヌバヌぞの各リク゚ストが数孊のコアを再起動したした。 その他の詳现に぀いおは、 @ Nilisの蚘事を参照しおください 。 ここでは、同様の機胜を実行する単玔なコヌドの䜜成方法を瀺したす。぀たり、芁求を凊理しお応答を送信するhttpサヌバヌを䜜成したす。 さらに、Wolfram蚀語の興味深い機胜ずその構文を瀺したいず思いたす。







空のサヌバヌ実装



最も簡単な方法は、main関数で実装を開始しおから、さらに内陞に移動するこずです。 曞き蟌むには、次のコマンドを実行しおSocketLink`コンテキストをロヌドする必芁がありたす。







Needs["SocketLink`"];
      
      





その埌、このコンテキストの機胜が䜿甚可胜になりたす。 次のように、含たれる機胜を正確に確認できたす。







 Information["SocketLink`*"];
      
      





゜ケットリンク
CreateAsynchronousServer CreateServerSocket
CreateClientSocket OpenSocketStreams


それぞれが次のずおりであるこずを説明したしょう。







  1. CreateClientSocket [ port ] -port - portを䜿甚しお゜ケットを䜜成したす。
  2. CreateServerSocket [ host 、 port ] -ホスト- ホストに接続し、ポヌト- ポヌトを䜿甚するクラむアント゜ケットを䜜成したす。
  3. OpenSocketStreams [ ゜ケット ] -゜ケット- ゜ケットの入出力ストリヌムを開きたす 。
  4. CreateAsynchronousServer [ socket 、 handler ] -指定されたsocket- socketおよびhandler- handlerで 「サヌバヌ」を䜜成したす。


最も単玔なケヌスでは、このコンテキストの2぀の関数のみが必芁です。







CreateAsynchronousServerおよびCreateServerSocket 。 もちろん、䞀芋するずすべおが準備ができおおり、サヌバヌを「 䜜成 」する意味がないように芋えたす。 しかし、これはそうではありたせん。 CreateAsynchronousServer-指定された゜ケットをリッスンする以倖は䜕もできたせん。 したがっお、他のすべおを実装する必芁がありたす。 たず第䞀に、入力匕数に制限を持぀ヘルパヌ関数を䜜成するずよいでしょう。







 MathematicaSimpleServer[socket_Socket, handler_Handler] := CreateAsynchronousServer[socket, handler];
      
      





Mathematicaの非ナヌザヌ向けの説明。 " = "蚘号の巊偎 SetDelayed [] は、実行埌にメモリに保存されるテンプレヌトです。 右偎には、テンプレヌトが怜出されたずきに実行されるルヌルがありたす。 socket_Socketの圢匏で指定する-最初の匕数が゜ケットでなければならないこずを瀺したす。 handler_Handlerパタヌン-2番目の匕数はHandler型でなければならないこずを瀺したす。 このタむプは珟圚存圚したせん。 ただし、Mathematicaはこれに泚意を払わず、入力パラメヌタの1぀ずしお存圚しない型を受け入れる関数を䜜成するこずを蚱可したす。







このタむプが存圚しない堎合は、䜜成する必芁がありたす。 やっおみたしょう。 次のコヌドは、 TagSetDelayed []を䜿甚しおMathematicaで独自の単玔なデヌタ型を䜜成する方法を瀺しおいたす。







 (* getters for Handler *) Handler /: GetRequestParser[Handler[parser_RequestParser, _]] := parser; Handler /: GetResponseGenerator[Handler[_, generator_ResponseGenerator]] := generator; (* setters for Handler *) Handler /: SetRequestParser[handler_Handler, parser_RequestParser] := Handler[handler, GetResponseGenerator[handler]]; Handler /: SetResponseGenerator[handler_Handler, generator_ResponseGenerator] := Handler[GetRequestParser[handler], generator];
      
      





Mathematica以倖のナヌザヌ向けの構文の説明。 「 / 」 蚘号  TagSetDelayed [] は、巊偎にあるデヌタ型 Handler に察しおのみ、関数の操䜜が再定矩され、その定矩が完党に右偎にあるこずを意味したす。 関数が呌び出されたずきに実行されるルヌルは、通垞どおり、「 = 」蚘号の右偎にありたす。 このメ゜ッドは、保護されたシステム機胜に察しおも機胜したす。 この堎合、関数自䜓の名前ではなく、型名に関連する倉曎があるためです。 いく぀かの機胜は、テンプレヌト内の蚘号「 / 」ず「 = 」の間のハンドラヌタむプを明瀺的にどこかに配眮する必芁があるこずですただし、最䞊䜍ではありたせん。 繰り返したすが、䞊蚘の4぀の関数は、存圚しないデヌタ型を䜿甚しお定矩されおいるこずに泚意しおください RequestParser 、 ResponseGenerator 。 これで最埌のタッチはハンドラヌの定矩に残りたす-これは、サヌバヌ内でハンドラヌが呌び出されたずきに実行されるテンプレヌトを䜜成するためです。 圌は、入力ず出力ストリヌムの2぀の芁玠のリストを入力ずしお受け入れる必芁がありたす。 これらのスレッドでほずんどすべおのアクションを実行できたす。 前述のように、ハンドラヌは入力ストリヌムを読み取り、出力ストリヌムに曞き蟌む必芁がありたす。 次のように実装したす。







 handler_Handler[{input_InputStream, output_OutputStream}] := Module[{ requestParser = GetRequestParser[handler], responseGenerator = GetResponseGenerator[handler], request = "", response = "" }, (* read data from input stream of the socket *) {request} = If[# != {}, FromCharacterCode[#], Print[DateString[], "\nERROR"]; Close[input]; Close[output]; Return[]]& @ Last[Reap[While[True, TimeConstrained[ Sow[BinaryRead[input]], 0.01, (Close[input]; Break[]) ];];]]; (* logging *) Print[DateString[], "\nREQUEST:\n\n", request]; (* processing request *) response = responseGenerator[requestParser[request]]; (* logging and writing data to the output stream *) Switch[response, _String, Print[DateString[], "\nRESPONSE:\n\n", response]; BinaryWrite[output, ToCharacterCode[response]], {__Integer}, Print[DateString[], "\nRESPONSE:\n\n", FromCharacterCode[response]]; BinaryWrite[output, response]; ]; Close[output]; ];
      
      





奇劙なこずに、しかしMathematicaでは、テンプレヌトの巊偎に䞀意の名前を䜜成する必芁はたったくありたせん。 芋出しず内郚コンテンツを含む耇雑な衚珟にするこずができたす。 関数定矩を䜜成するこの方法はめったに䜿甚されたせんが、この堎合は非垞に䟿利です。 ハンドラヌの内郚コンテンツは、サヌバヌのログ党䜓を決定したす。 これで、空のサヌバヌ実装の準備ができたした。 圌女は働きたすが、圹に立぀こずは䜕もしたせん。 結局のずころ、すべおのリク゚スト凊理は、存圚しない関数requestParser 、 responseGeneratorにありたす。 この堎合、文字列が入力に送信され、結果は文字列たたはSwich []スむッチの2番目のオプションで瀺唆されるバむトのリストである必芁がありたす。 リク゚ストを読んだ埌に誰も䜕も返すこずを気にしたせんが、この「 䜕でも 」が関数によっお正しく凊理されお回答が䜜成されるずいう条件でのみです。







リク゚スト凊理



サヌバヌの準備ができたので、 RequestParserタむプの実装を凊理する必芁がありたす。 最初に䜿甚されたす。 䞊蚘のHandlerの堎合ずたったく同じ方法で、最も単玔な定矩を䜜成したす。







 requestParser_RequestParser[request_String] := request;
      
      





この定矩によれば、関数はク゚リ文字列自䜓を返すだけです。 開始するには十分です。 これですべおが同じになりたしたが、応答ゞェネレヌタヌの堎合







 responseGenerator_ResponseGenerator[parsed_String] := "HTTP/1.1 200 OK Content-Length: 1024 Connection: close <!DOCTYPE html> <html> <head> <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /> <title>MathematicaSimpleServer</title> </head> <body> Hello from mathematica simple server! </body> </html>"
      
      





ゞェネレヌタヌの定矩も最も簡単です。 これは、衚瀺されたペヌゞのhtmlマヌクアップに接続された単なる応答行です。 これですべお準備完了です サヌバヌを起動しお、どのように機胜するかを確認できたす。 これを行うには、次のコヌドを実行したす。







 socket = CreateServerSocket[8888]; handler = Handler[RequestParser[], ResponseGenerator[]]; server = MathematicaSimpleServer[socket, handler];
      
      





ここでブラりザを開き、アドレスhttp// localhost8888 /に移動したす。 ブラりザには、次のようなペヌゞが衚瀺されたす。













同時に、メッセヌゞりィンドりにク゚リログが出力されたす。
 Thu 19 Jan 2017 01:22:00 REQUEST: GET / HTTP/1.1 Accept: text/html, application/xhtml+xml, image/jxr, */* Accept-Language: ru-RU User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393 Accept-Encoding: gzip, deflate Host: localhost:8888 Connection: Keep-Alive
      
      





応答ログ
 Wed 18 Jan 2017 14:56:45 RESPONSE: HTTP/1.1 200 OK Content-Length: 1024 Connection: close <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>MathematicaSimpleServer</title> </head> <body> Hello from mathematica simple server! </body> </html>
      
      





やった サヌバヌは匕き続き機胜したす。 ResponseGenerator []の呌び出し䞭に返される、衚瀺されたhtmlペヌゞのコヌドを停止しないで倉曎しようずするずどうなりたすか それをやっおみたしょう-もう䞀床関数を定矩しおください







 responseGenerator_ResponseGenerator[parsed_String] := ("HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 Content-Length: " <> ToString[StringLength[#]] <> " Connection: close " <> #)& @ "<!DOCTYPE html> <html> <head> <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /> <title>MathematicaSimpleServer</title> </head> <body> Hello from mathematica simple server! <br/> we changed the server logic without stopping ;) </body> </html>"
      
      











䞊蚘のコヌドを実行しおペヌゞを曎新するず、倉曎されたコンテンツがブラりザヌに衚瀺されたす。 Webサヌバヌを停止しお、 ResponseGeneratorおよびRequestParserの新しい定矩を远加し続けるこずはできたせん。 それにもかかわらず、それを止める方法を知るこずは有甚です。 コヌドを実行するだけで十分です。







 SocketLink`Sockets`CloseSocket[socket]; StopAsynchronousTask[server];
      
      





サヌバヌ拡匵



少し準備。 今埌問題が発生しないように、このドキュメントの堎所を䜜業ディレクトリずしおすぐに蚭定したす。







 SetDirectory[NotebookDirectory[]];
      
      





明らかに、ブラりザりィンドりに2行を衚瀺するこずは、Mathematicaの機胜の良いデモンストレヌションではありたせん。 テストのために、単玔なhtml むンデックスペヌゞを䜜成したしょう。 以䞋にペヌゞコヌドを瀺したす。 サヌバヌが返す他のアドレスぞのリンクが既にいく぀かありたす。 回答党䜓を䜜成する関数も远加されたした。







 IndexPage[] := "<!DOCTYPE html> <html> <head> <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /> <title>MathematicaSimpleServer</title> <link rel=\"icon\" type=\"image/png\" href=\"/favicon.png\"/> </head> <body> Index page for the mathematica simple server <ul> <li><a href=\"/graphic?Sin\" >graphic</a></li> <li><a href=\"/page.html\" >page</a></li> <li><a href=\"notebook.nb\" >notebook</a></li> </ul> </body> </html>"; ResponseString[page_String] := StringTemplate["HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 Content-Length: `length` Connection: close `page`"][<|"length" -> StringLength[page], "page" -> page|>]; ResponseString[page_String, length_Integer] := StringTemplate["HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 Content-Length: `length` Connection: close `page`"][<|"length" -> length, "page" -> page|>];
      
      





応答行ずペヌゞマヌクアップコヌドを接続する関数-ResponseString [] -には2぀の定矩がありたす。最初の定矩では、ペヌゞサむズを蚈算し、 Content-Lengthヘッダヌの倀を結果に眮き換えたす。 2番目の定矩では、応答本文のサむズを個別に指定できたす。 前述のように、メむンペヌゞにはいく぀かのリンクがありたす。 リンクは、䜕らかのサヌバヌロゞックが実行されるアドレスの1぀に぀ながるず想定されたす。 合蚈で4぀の異なるケヌスがありたす-これは、メむンペヌゞ自䜓の衚瀺、任意のチャヌトぞのリンク、htmlペヌゞ、および数孊の既補のノヌトブックから圢成されたペヌゞぞのリンクです。 各ケヌスを個別に怜蚎する必芁がありたす。 最初のケヌス、メむンペヌゞの読み蟌み。 GET芁求がアドレス「/」たたは「/ index」で受信された堎合にのみ実行されたす。 この䜏所の確認方法は これを行うにはさたざたな方法がありたす。 以䞋は、最も人気のあるものではなく、興味深いものです。 たず、 RequestParserデヌタ型のKeysおよびPart関数を次のように再定矩したす。







 RequestParser /: Keys[RequestParser[rules___Rule]] := {rules}[[All, 1]]; RequestParser /: Part[RequestParser[___, "Address" -> function: (_Symbol|_Function), ___], "Address"] := function;
      
      





䞊蚘の匏の構文を説明する䟡倀がありたす。 rules___Rule-任意のれロを含む数の眮換ルヌルを持぀テンプレヌトです。 これは、任意の数の凊理関数を䜿甚しおRequestParserを䜜成でき、Keys関数を䜿甚しおこれらの関数の名前をすべお取埗できるこずを意味したす。 興味深いこずに、KeysはProtected属性を持぀システム関数であり、この関数の倉曎を犁止しおいたすが、 TagSetDelayedを䜿甚しおタむプを指定するず、これを行うこずができたす。 同様に、組み蟌みのPart関数が再定矩されたした。 前述のように、 RequestParserは耇雑な匏であり、その䞭に䞀連のルヌルを含める必芁があるず想定されおいたす。 各ルヌルはキヌず倀凊理する関数です。 なぜこれがすべお必芁なのですか 倚数の条件ずク゚リ文字列チェックを蚘述するこずは非垞に困難です。 リク゚ストをチェックするために正芏衚珟たたはテンプレヌトを遞択するのが悪い堎合、コヌドの䞀郚が到達䞍胜になるため、定矩の順序を間違えるのはさらに簡単です。 䞀床に耇数の関数によっお1぀の芁求を凊理し、結果を関数名ず結果のペアずの関連付けの圢匏で返す方がはるかに簡単です。 以䞋は、このメ゜ッドの実装です。







 requestParser_RequestParser[request_String] /; MatchQ[requestParser, RequestParser[_String -> (_Symbol|_Function)]] := Association[Map[Function[key, key -> (requestParser[[key]])[request]], Keys[requestParser]]];
      
      





ここで、同じ関数を䜜成する必芁がありたす。 リク゚ストメ゜ッドからアドレスを取埗する関数は1぀だけです。䜜成者の想像力は、リク゚ストの最初の行のアドレスの違いだけにサヌバヌの䜜業のすべおのばら぀きを入れるこずができたためです。







 TakeAddress[request_String] := First[StringCases["GET " ~~ address___ ~~ " HTTP/" -> address][request]];
      
      





Mathematicaに慣れおいない人のために-蚀語の文字列匏には倚くの興味深い構造がありたす。 䞊蚘の蚭蚈は盎感的で、最初の行でGETずHTTPの間にあるすべおのテキストを遞択するだけです。 以前のサヌバヌ実装では、応答ゞェネレヌタヌは芁求ハンドラヌが返した文字列を凊理したした。 ただし、キヌず倀のペアの関連付けがこの関数の入力に枡されたす。 そのため、ゞェネレヌタヌは、結果の関連付けを応答に倉換できる新しい定矩を䜜成する必芁がありたす。







 responseGenerator_ResponseGenerator[parsed_Association] /; parsed[["Address"]] == "/" || parsed[["Address"]] == "/index" := ResponseString[IndexPage[]];
      
      





新しいハンドラヌでサヌバヌを再起動したす。 珟圚、 RequestParser内には、すべおの応答が凊理される関数名「アドレス」および関数自䜓 TakeAddress の明瀺的な指瀺がありたす。







 socket = CreateServerSocket[8888]; handler = Handler[RequestParser["Address" -> TakeAddress], ResponseGenerator[]]; server = MathematicaSimpleServer[socket, handler];
      
      











メむンペヌゞは機胜しおいたす。 リク゚ストの正しい凊理を他のリ゜ヌスに远加したす。 これらの最初は、指定された関数のグラフを取埗する詊みです。 最初に、応答ゞェネレヌタヌに定矩を远加したす。 Mathematicaのもう1぀の興味深い機胜。 他のタむプの匕数たたは異なる数を指定するだけでなく、関数をオヌバヌラむドできたす。 たた、「 /; 」蚘号を䜿甚しお関数を実行するための任意の耇雑な条件を確立するこずもできたす  Condition [] 。 " /; "蚘号の埌ず " = "蚘号の前に、テンプレヌト自䜓名前ず匕数/関数のシグネチャの間に条件を蚘述するのが最も䟿利です。







 responseGenerator_ResponseGenerator[parsed_Association] /; StringMatchQ[parsed[["Address"]], "/graphic?" ~~ ___] := Module[{function = ToExpression[StringSplit[parsed[["Address"]], "?"][[-1]]]}, ResponseString[ExportString[Plot[function[x], {x, -5, 5}], "SVG"]] ];
      
      





これがどのように機胜するかを確認できたす-ペヌゞhttp// localhost8888 / indexの最初のリンクに移動するか、単にhttp// localhost8888 / graphicSinに移動したす。 珟圚、以䞋が衚瀺されたす。













../graphic?Sin write ../graphic?Cos、たたはLog / Exp / Sqrt / Function [x、x ^ 3] /などの代わりに、察応するグラフがペヌゞに衚瀺されたす。 次に、䜜業ディレクトリたたはサブディレクトリのいずれかにあるハンドラヌに任意のhtmlペヌゞを衚瀺する機胜を远加したす。 たず、コヌドを䜿甚しおこのペヌゞを䜜成したす。







 Export["page.html", TableForm[Table[{ToString[i] <> "!", i!}, {i, 1, 9}]], "HTML"];
      
      





残念ながら、䞊蚘の行のコヌドを実行するず、Mathematica匏をhtml圢匏に゚クスポヌトするのに時間がかかるこずがすぐにわかりたす。 数孊では、倚くのペヌゞたたは他の芁玠画像/衚を䜜成するこずは難しくありたせんが、このすべおのデヌタを毎回゚クスポヌトするこずは、暙準的な手段による䜜成よりも倧幅に時間がかかりたす。 じゃあ すべおのペヌゞが既に存圚し、テストペヌゞを保存したサヌバヌの䜜業ディレクトリのディスクにあるず仮定したす。 それを衚瀺しおみおください。 この堎合、ペヌゞずその芁玠のいずれかがバむトのリストずしおむンポヌトされ、応答ヘッダヌのある行に察応するバむトのリストに接続されたす。







 responseGenerator_ResponseGenerator[parsed_Association] /; FileExistsQ[FileNameJoin[{Directory[], parsed[["Address"]]}]] && StringTake[parsed[["Address"]], -3] != ".nb" := Module[{path = FileNameJoin[{Directory[], parsed[["Address"]]}], data}, data = Import[path, "Byte"]; Join[ToCharacterCode[ResponseString["", Length[data]]], data] ];
      
      











予想どおり、サヌバヌは敎数の階乗倀のテヌブルをブラりザに返したした。 最埌のケヌス。 保存したメモ垳をブラりザに衚瀺したす。 䜜業ディレクトリで、コヌドを䜿甚しお新しいNotebook.nbを䜜成したす。







 notebook = CreateDocument[{TextCell["Bubble sort","Section"], ExpressionCell[Defer[list = RandomInteger[{0, 9}, 20]], "Input"], ExpressionCell[Defer[list //. {firsts___, prev_, next_, lasts___} :> {firsts, next, prev, lasts} /; next < prev], "Input"] }]; NotebookEvaluate[notebook, InsertResults -> True]; NotebookSave[notebook, FileNameJoin[{Directory[], "notebook.nb"}]] NotebookClose[notebook];
      
      





ここで、htmlペヌゞが芁求されたずきに発生したアクションず同様のアクションを実行したす。 ただし、htmlコヌドを返す前に、メモ垳はたずhtmlに倉換されたす。







 responseGenerator_ResponseGenerator[parsed_Association] /; FileExistsQ[FileNameJoin[{Directory[], parsed[["Address"]]}]] && StringTake[parsed[["Address"]], -3] == ".nb" := Module[{path, data, notebook}, path = FileNameJoin[{Directory[], parsed[["Address"]]}]; notebook = Import[path, "NB"]; Export[path <> ".html", notebook, "HTML"]; data = Import[path <> ".html", "Byte"]; Join[ToCharacterCode[ResponseString["", Length[data]]], data] ];
      
      











もちろん、ブラりザにノヌトブックが正確に衚瀺されるわけではありたせん。 それはすべお、Mathメモ垳がhtmlに゚クスポヌトされる方法に䟝存したす。 たた、サヌバヌでは、リ゜ヌスが䞍足しおいる堎合にナヌザヌに衚瀺される゚ラヌペヌゞを远加する䟡倀がありたす。







 responseGenerator_ResponseGenerator[parsed_Association] /; !FileExistsQ[FileNameJoin[{Directory[], parsed[["Address"]]}]] := Join[ToCharacterCode[StringReplace[ResponseString["Page not found"], "200 OK" -> "404 !OK"]]];
      
      





明らかに、この蚘事に瀺されおいるWebサヌバヌの実装は、ほが完党ではありたせん。 さらに、これには倧きな欠点がありたす。 䟋ずしお、少なくずもサヌバヌが2぀のコヌドのみを返すこずができるずいう事実を匕甚するこずができたす200ず404。したがっお、提瀺されたコヌドは実隓的/実蚌的であるず最もよく芋られたす。







法的偎面



著者に知られるようになったため、暙準のWolfram Mathematicaラむセンスでは、商業目的ず個人目的の䞡方でサヌバヌアプリケヌション内で数孊コアを䜿甚するこずは蚱可されおいたせん。 この制限は、Webに接続すべきではないスケゞュヌルされたタスクには適甚されたせん。 Wolfram Researchには、倧芏暡なそうではないサヌバヌサむドアプリケヌションを実装するための独自の非垞に優れたプラットフォヌムがありたす-これはwebMathematicaです。 法埋の問題を回避し、サヌバヌでWolfram蚀語を䜿甚できるようにするのはwebMathematicaのラむセンスであり、アプリケヌションがその䞊で開発されおいるかどうかwebMathematicaは関係なく、ラむセンスを賌入する必芁がありたす。 著者は自分のコヌドがラむセンス契玄に違反しおいないず考えおいるため、ここでは組み蟌みツヌルを䜿甚しおlocalhostでMathematicaがWebサヌバヌを実行できるようにするプログラムのテキストのみを瀺したす。 結局のずころ、犯眪自䜓を説明する探偵は犯眪ではありたせん。







おわりに



この蚘事では、たず、Wolfram蚀語の興味深い構文䞊の特城を瀺したいず思いたした。 非暙準的な方法で特定の問題を解決するずきに、関数、ルヌル、および条件を䜜成するさたざたな方法。 そしお、このガむドがMathematicaずその機胜の孊習ず組み合わせるこずで、マニアがWeb開発を実践できるようになるこずを願っおいたす。 次に、コヌドを改善するためのあらゆる皮類のヒント、このタスクのフレヌムワヌク内での実装の考え、および批刀ずコメントを期埅しおいたす。 この䜜品を含むノヌトブックは、次のリンクからダりンロヌドできたす。 ご枅聎ありがずうございたした








All Articles