Wolfram言語上のパーソナルWebサーバー

ルートロジックがWolfram言語であるWebサーバーをすばやく作成したい場合があります。 正しい方法と長い方法があります。 報酬は、ソリューションとパフォーマンスの美しさです。 そして、2番目の方法があります。 彼について話しましょう。



約6か月前にMathematicaとWolfram言語を積極的に研究し始めましたが、すぐにさまざまな日常業務や仕事のタスクで「日常」言語として使用したいという要望がありました。 たとえば、何らかのデータ収集を分析したり、複数のシステムを相互に接続したりする必要がある場合、誰もが最初に思い浮かぶ言語を持っています。 通常、これはかなり高レベルのスクリプト言語の一種です。 私の場合、Pythonがこの役割を果たしましたが、その後、彼は深刻な競争相手になりました。



ただし、Mathematicaノートブックを実行し、そこからコードを実行してもすべてが解決できるわけではありません。 一部のタスクでは、定期的な実行またはイベントでの起動が必要です。 サーバーが必要です。 まず、 会社が提供する展開および実行オプションを見てみましょう。 私が知る限り、オプションは次のとおりです。

1)古き良きMathematicaノートブック。 言い換えると、GUIでの1回限りの作業セッションです。

2) Wolfram Cloud これは素晴らしいオプションで、私も使用しています。 ただし、クラウドオプションが機能しない理由は多数あります。 そのうちの1つだけに名前を付けます。各通話の費用はゼロではありません。 多くの小規模な定期操作では、特にアイドル容量が手元にある場合、これは不当に高価になる可能性があります。

3)Wolfram Private Cloud。 独自のクラウドを起動する何らかの将来の機会のように思えます。 詳細は不明です。

4) Wolfram Symbolic Transfer Protocolを使用します。 Wolfram言語をシステムに統合する最も徹底的で普遍的な方法のように見えます。 ここのサーバーは特定のアプリケーションの1つにすぎません。 それが「正しい道」です。

5) Wolfram Script すべてが簡単です-グラフィカルインターフェイスに直接参加することなく、他のスクリプトと同様にWolfram言語コードを呼び出します。 Cron、パイプライン、その他の優れたメカニズムを自由に使用できます。 このメソッドを使用して、サーバーをすばやく作成します。



サーバーから直接、何でも選択できます。私の場合はTornadoです。 引数、ヘッダー、リクエストボディをスクリプトに送信し、その作業の結果を読み取る単純なハンドラーを作成します。



import tornado.ioloop import tornado.web import os, subprocess import json WOLFRAM_EXECUTABLE = "wolfram" def execute(arguments): def run_program(arguments): p = subprocess.Popen(arguments, stdout=subprocess.PIPE, stderr=subprocess.PIPE) return iter(p.stdout.readline, b'') res = '' for line in run_program(arguments): res+=line return res class MainHandler(tornado.web.RequestHandler): def get(self): out = execute([WOLFRAM_EXECUTABLE,"-script", "main.m", str(self.request.method), str(json.dumps(self.request.arguments)), str(json.dumps(self.request.headers)), str(self.request.body)]) self.write(out) application = tornado.web.Application([ (r"/", MainHandler), ]) application.listen(8888)
      
      







実際、「main.m」はWolfram言語スクリプトです。 その中で、渡された引数を受け取って解釈し、結果を返す必要があります。



 method = $CommandLine[[4]] arguments = Association @ ImportString[$CommandLine[[5]], "JSON"] headers = Association @ ImportString[$CommandLine[[6]], "JSON"] body = If[Length[$CommandLine] >= 7,$CommandLine[[7]], ""] Print["Hello world"]
      
      







スクリプトには「Hello world」と表示されます。 python部分は、このデータをクライアントに正直に返します。

原則として、これはメソッドの本質です。



このフォームでは、サーバーは200の結果コードを持つ文字列データのみを送受信できます。もう少し柔軟性が必要です。 これを行うには、スクリプトからのデータを文字列としてだけでなく、何らかの構造化された形式で送信する必要があります。 したがって、JSONへの別の変換があります。 形式は次のようになります。



 { “code”: 200, “reason”: OK, “body”: “Hello world" }
      
      







次に、反対側で正しく処理する必要があります。



 outJson = json.loads(out) self.set_status(outJson["code"], outJson["reason"]) if(outJson["body"] != None): self.write(str(outJson["body"]))
      
      







次のステップは、テキストだけでなく他のデータも返す機能を追加することです。 おそらく、2つのダブルJSON変換は、誰かにとって十分に遅いソリューションではないように思われます... JSONに「file」フィールドと「contentType」フィールドを追加します。 「file」フィールドが空でない場合、「body」フィールドの内容を出力ストリームに書き込む代わりに、指定されたファイルを読み取ります。



 outJson = json.loads(out) self.set_status(outJson["code"], outJson["reason"]) if(outJson["file"] != None): self.add_header("Content-Type", outJson["contentType"]) with open(outJson["file"], 'rb') as f: while True: data = f.read(16384) if not data: break self.write(data) self.finish() os.remove(outJson["file"]) elif(outJson["body"] != None): self.write(str(outJson["body"]))
      
      







これを、呼び出されたスクリプトの側から見てみましょう。 応答を生成するためのいくつかの方法:



 AsJson[input_] := ExportString[Normal @ input, "JSON"] HTTPOut[code_, body_, reason_] := <|"code"->code, "body"->body, "reason"->reason, "file"->Null|> HTTPOutFile[expression_, exportType_, contentType_] := Module[{filePath = FileNameJoin[{$TemporaryDirectory, "httpOutFile"}]}, Export[filePath, expression, exportType]; <|"code"->200, "body"->Null, "reason"->Null, "file"->filePath, "contentType"->contentType|> ]
      
      







最後に、特定のメソッドのハンドラーを作成します。



 HTTPGet[arguments_, headers_] := AsJson[...] Switch[method, "GET", HTTPGet[arguments, headers], "POST", HTTPPost[arguments, headers, body]]
      
      







したがって、メソッドHTTPGet、HTTPostなどが表示されます。 ビジネスロジックを作成するときです。 さまざまなパス(「/」、「/ SomeEndpoint」など)のハンドラーを作成できますが、代わりに、呼び出される関数を決定する引数「/?Op = MyFunction」を呼び出しに追加します。

スクリプトにこの関数を選択して呼び出すロジックを追加するだけです。 ToExpression []を使用します。



 HTTPGet[arguments_, headers_] := Module[{methodName = "GET"<>arguments["op"]}, AsJson[ToExpression[methodName][arguments, headers]] ]
      
      







これで、GETMyFuction関数を追加するだけで、ビジネスロジックの最初のユニットの準備ができました。 この関数に現在の時刻を表示させます。



 GETMyFuction[arguments_, headers_] := HTTPOut[ToString[Now]]
      
      







出力画像の例を示すために残っています。 また、入力パラメーターを使用していないため、任意の数の要素に一致する匿名パターンでそれらを指定します。



 GETTestGraph[___] := Module[{}, out = Graph[{a -> e, a -> c, b -> c, a -> d, b->d, c->a}]; HTTPOutFile[out, "PNG", "image/png"] ]
      
      







ブラウザで「... /?Op = TestGraph」を開くと、次の画像が表示されます。



画像



それだけでいい一日を!



All Articles