Sometimes people want to quickly make a web server, the root logic of which will be on the Wolfram Language. There is a right and long way. The reward will be the beauty of the solution and performance. And there is a second way. We will talk about him.
I began to actively study Mathematica and Wolfram Language about six months ago and immediately there was a desire to use it as an “everyday” language for various household and near-work tasks. You know, everyone has a language that comes to mind first, if you need to, say, analyze a collection of data or link several systems with each other. Usually this is some rather high-level scripting language. In my case, Python acted in this role, but then he had a serious competitor.
However, not everything can be solved by launching the Mathematica notebook and once having executed the code from it. Some tasks require periodic execution or launch on some event. Need a server. First, let's see what options the deployment and execution of
the company itself . As far as I can tell, the options are as follows:
1) Good old Mathematica Notebook. In other words, a one-time work session in the GUI.
2)
Wolfram Cloud . And this is a great option that I use as well. However, there are many reasons why the cloud option may not work. I will name only one of them - each call costs a non-zero amount of money. For many small periodic operations, this may be unnecessarily expensive, especially when there are idle powers at hand.
3) Wolfram Private Cloud. Sounds like some imminent opportunity to launch your own cloud. The details are unknown to me.
4) Use
Wolfram Symbolic Transfer Protocol . It looks like the most thorough and universal way to integrate Wolfram Language into your system. The server here is just one of the particular applications. The very “right and long way”.
5)
Wolfram Script . It's simple - we call the code on Wolfram Language like any other script, without the direct participation of the graphical interface. Cron, pipeline and all the other wonderful mechanisms at our disposal. We use this method to quickly create a server.
Directly by the server, we can choose anything, in my case it is Tornado. We write the simplest handler, which will send arguments, headers and the request body to our script and read the results of its work.
')
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)
Actually, “main.m” is our Wolfram Language script. In it, we need to get and interpret the passed arguments, as well as return the result.
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"]
Our script displays “Hello world”. The part on the python, in turn, honestly returns this data to the client.
In principle, this is the essence of the method.
In this form, our server will be able to receive and return only string data with a result code of 200. I want a little more flexibility. To do this, the data from the script must be transmitted not just as a string, but in a structured form. So we have another transformation in JSON and back. The format will be:
{ “code”: 200, “reason”: OK, “body”: “Hello world" }
Now it needs to be properly processed on the other side.
outJson = json.loads(out) self.set_status(outJson["code"], outJson["reason"]) if(outJson["body"] != None): self.write(str(outJson["body"]))
The next step is to add the ability to return not only text, but other data as well. Perhaps two double JSON transformations seemed to someone not a slow enough solution ... Add the “file” and “contentType” fields to our JSON. If the “file” field is non-empty, then instead of writing to the output stream the contents of the “body” field, we read the specified file.
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"]))
Let's look at all this from the side of the called script. A couple of methods for generating a response:
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|> ]
Finally, we write handlers for specific methods.
HTTPGet[arguments_, headers_] := AsJson[...] Switch[method, "GET", HTTPGet[arguments, headers], "POST", HTTPPost[arguments, headers, body]]
Thus, the HTTPGet, HTTPost, and similar methods appear. It's time to create business logic. You can create handlers for various paths (“/“, “/ SomeEndpoint”, etc.), but instead we add an argument to the call that defines the function to be called: “/? Op = MyFunction”.
It remains only to add the logic of choosing and calling this function in our script. Use
ToExpression [] .
HTTPGet[arguments_, headers_] := Module[{methodName = "GET"<>arguments["op"]}, AsJson[ToExpression[methodName][arguments, headers]] ]
Now you can simply add the GETMyFuction function and the first unit of business logic is ready. Let this function display the current time:
GETMyFuction[arguments_, headers_] := HTTPOut[ToString[Now]]
It remains to give an example of the output image. And since we do not use input parameters, we denote them by an unnamed
pattern that matches any number of elements.
GETTestGraph[___] := Module[{}, out = Graph[{a -> e, a -> c, b -> c, a -> d, b->d, c->a}]; HTTPOutFile[out, "PNG", "image/png"] ]
Now, when opening the browser “... /? Op = TestGraph”, you can see the following image:

That's all for a good day!