I've talked about threading a little already.
https://beomagi.blogspot.com/2019/09/python-threading.html
https://beomagi.blogspot.com/2019/09/threading-continued-timing-thread.html
Next up is a more complete application. Lets look at a simple multithreaded web service. Now this can be used for basic webpage services, but I think it really shines for making a REST API for quick tools.
Source:
https://github.com/beomagi/BasicRestApp/blob/main/http_server_mt3.py
snapshot below:
Discussion:
I've highlighted different sections of the code by altering the background of the div.
The bottom section with the dark purple background is our main area - could stick this in a construct like "if __name__ == '__main__':" but it's not necessary for our purposes.
The main here does a check for passed parameters to override the default port the application will be served on. Then we have an infinite loop that keep calling handle_request(). The "flush" before that forces any buffering in the io to be handled before the next handle. Useful for problems where buffered output isn't yet written at the time of a crash.
The main section makes a call to ThreadedHTTPServer. This manages threaded calls to the handler(3rd section). The handler implements calls for GET and POST.
I've added some simple functions on POST calls for adding 2 numbers, storing, and getting values in a key-value store sort of setup.
Interfacing with POST can be done from any other language. It's also nice to use CURL to test REST interfaces.
e.g.
beomagi@BeoBalthazar ~/gits/beomagi/BasicRestApp (main) 2021-01-29 13:35:09
└─ $ ∙ curl 127.0.0.1:8000
this is a test of the multithreaded webservice
beomagi@BeoBalthazar ~/gits/beomagi/BasicRestApp (main) 2021-01-29 13:35:16
└─ $ ∙ curl 127.0.0.1:8000/time
2021-01-29 18:35:21
beomagi@BeoBalthazar ~/gits/beomagi/BasicRestApp (main) 2021-01-29 13:35:16
└─ $ ∙ curl 127.0.0.1:8000 -X POST --data '{"cmd":["add",51,15]}'
66
So this example shows how we can make a processing request to the server. Obviously this can be more complex. Maybe I'm sending a url to be parsed (get me ma comix!). Maybe I'm passing data I want to store. Numerous reasons for this. What's performance like?
beomagi@BeoBalthazar ~/gits/beomagi/BasicRestApp (main) 2021-01-29 13:35:16
└─ $ ∙ curl 127.0.0.1:8000 -X POST --data '{"cmd":["put","b","If A Technological Feat Is Possible, Man Will Do It. Almost As If It is Wired Into The Core Of Our Being."]}'
{"b": "If A Technological Feat Is Possible, Man Will Do It. Almost As If It is Wired Into The Core Of Our Being."}
beomagi@BeoBalthazar ~/gits/beomagi/BasicRestApp (main) 2021-01-29 13:35:16
└─ $ ∙ curl 127.0.0.1:8000 -X POST --data '{"cmd":["get","b"]}'
If A Technological Feat Is Possible, Man Will Do It. Almost As If It is Wired Into The Core Of Our Being.
Ok, so that's the get and put app level functions working. Let's try a bunch of gets.
beomagi@BeoBalthazar ~/gits/beomagi/BasicRestApp (main) 2021-01-29 13:35:16
└─ $ ∙ time (for a in {1..1000}; do (curl -s 127.0.0.1:8000 -X POST --data '{"cmd":["get","b"]}' ) & done |sort | uniq ; wait )
If A Technological Feat Is Possible, Man Will Do It. Almost As If It is Wired Into The Core Of Our Being.
real 0m2.403s
user 0m4.234s
sys 0m20.688s
beomagi@BeoBalthazar ~/gits/beomagi/BasicRestApp (main) 2021-01-29 13:35:16
└─ $ ∙
The time it took is the real value. It took 2.4 seconds to pull this data a thousand times. If you want to pull a lot of data faster, this basic server can be expanded to use multi-put/multi-get
This isn't great though. We're severely limiting the test by relying on bash to loop though and spawn requests. Bash is slow for this :)
So let's try apache bench.
Start by putting the request in a file.
beomagi@BeoBalthazar ~/gits/beomagi/BasicRestApp (main) 2021-01-29 13:35:16
└─ $ ∙ cat postthis.txt
'{"cmd":["get","b"]}'
beomagi@BeoBalthazar ~/gits/beomagi/BasicRestApp (main) 2021-01-29 13:35:16
_____________________________________________________________
Using apachebench (ab) loading the queue to the max 128 concurrent requests, we handle 10k requests in 3.378 seconds.
I've used this in another system where getting outside software was quite restrictive. It was quicker to write something like the above to act as a cache for a monitoring system. The system would store various database counts and system checks for a monitoring system. Prior to that the monitoring system was making the DB queries directly and wow did our DB hate that!!. Granted it was far more fleshed out - there was get/put single value and multiple value commands. The default page would show all stored data, and the age of the data. There was a timing thread for stats on how heavy the system was being hit. While that system (cachewho) was written and made for that company, I do plan to rewrite in python3 (instead of 2).