diff --git a/README.md b/README.md index d9a82e4..6ce5a35 100644 --- a/README.md +++ b/README.md @@ -21,18 +21,24 @@ If you don't really now what a middleware is, be at ease, nobody really knows. Nowadays, you may have eared of their latest avatar: *web services*. As our programmer is going to realize, one now have *two* problems. The burden of writing, using, and maintaining code using middleware is always huge. -Because they are made to handle a **tremendous** number of complex situations, -most of which involve adversary users, users being bots, or both. +Because they are made to handle a **tremendous** number of features and complex situations +—most of which involve adversary users, users being bots, or both. But most of the time, the actual problem does not really involve these situations. -At least not at the beginning (which means probably never). -If you are building up (firsts versions of) communicating programs +At least not at the beginning (… which means probably never). +People familiar with middleware history would argue that their key feature +is not just *messages passing* but *remote call*, which involves *object serialization*. +But most of the time, the messages are pretty simple anyway, and using a middleware +to implement a serialization of a list of instances having three members of fundamental types +is not a good use of your time. + +If you are building up (firsts versions of) two communicating programs that will run on a (safe) local network, -and for which the exchanged messages are known, +and for which the exchanged messages are known and simple, then I have good news: **you don't have to use web services** (or any kind of middleware). -**You just need to know how to read/write from/to (special) files**. +**YOU JUST NEED TO KNOW HOW TO READ/WRITE FROM/TO (SPECIAL) FILES**. ### Overview @@ -49,7 +55,7 @@ in which you read/write. Once you made your service on top of named pipes, it is easy to wrap it within an interface made with other languages/tools. -For instance, it is very easy to expose it on the network using common tools like `socat`. +For instance, it is very easy to expose it on the network using common tools like `socat` (see below). Be warned that this is not secure, though, you should only use this for testing purpose in a secured local network. @@ -67,13 +73,13 @@ The theoretical principle can be represented by this UML sequence diagram: │ │ │ │ │ │ │┌──────╢ │ │ block││ wait ║ - │ask │ │└─────→║ - ├─────────────→│ │ - ╟─────┐│ ├──────→│ + │ask │ │└─────>║ + ├─────────────>│ │ + ╟─────┐│ ├──────>│ ║wait ││block │ ║process - ║←────┘│ │ ║ - │ │←──────────────┤ - │←─────┤ │ tell│ + ║<────┘│ │ ║ + │ │<──────────────┤ + │<─────┤ │ tell│ │ │ │ │ ``` @@ -84,6 +90,29 @@ Notes: for the sake of simplicity, but you may just as well use only one. +### When NOT to use named pipes + +To be completely honest, here are a list of cases that —**if they are all true**— +may lead you to consider that maybe it would be a good idea +to think about how you may eventually end up +looking for a solution that might be something that's close to a middleware: + +- ☒ your service takes time to compute something, +- ☒ you have one service, but an unknown (large) number of clients, +- ☒ all clients expect the same interface, +- ☒ which involves answering to the server, +- ☒ with *complex* data structures, +- ☒ you absolutely need to serve them all as fast as possible, +- ☒ over the internet, +- ☒ and you are *certain* that no one will want *another* middleware in the next project. + +I your use case don't match all of this checklist but you still want to +use a middleware, maybe you should just consider making a side software +that will expose/transliterate the data going through the named pipe. +That way, your service stays simple and you can easily +exchange one middleware for another without even touching it. + + Build and run ------------- diff --git a/service1.py b/service1.py new file mode 100755 index 0000000..c47ddad --- /dev/null +++ b/service1.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python + +import sys + +if __name__ == "__main__": + print("Start server") + + while True: + with open(sys.argv[1]) as fin: + datas = fin.readline() + + data = datas.strip() + print("Received: <",data,">", file=sys.stderr) + + with open(sys.argv[2], 'w') as fout: + fout.write(data) + + if data == "exit": + break + + print("Stop server", file=sys.stderr) diff --git a/service2.py b/service2.py new file mode 100755 index 0000000..0fd851f --- /dev/null +++ b/service2.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python + +from enum import Enum +import threading +import stat +import sys +import os + +class ERROR(Enum): + NOT_FIFO = 1 + +class Service: + def __init__(self, context: str, data: str, out: str) -> None: + self._has_current_context: bool = False + self._mutex = threading.Lock() + self._file_current_context: str = context + self._file_data: str = data + self._out: str = out + self._current_context: str = "" + + def get_has_current_context(self) -> bool: + return self._has_current_context + + def set_has_current_context(self, flag: bool) -> None: + self._mutex.acquire() + self._has_current_context = flag + self._mutex.release() + + def update_current_context(self) -> None: + while True: + print("Wait for context...", file = sys.stderr) + has_error: bool = False + try: + with open(self._file_current_context) as if_current_context: + self._current_context: str = if_current_context.readline().strip() + except: + has_error = True + + if not has_error: + self.set_has_current_context(True) + print("\tReceived context:", self._current_context, file=sys.stderr) + + def handle_data(self) -> None: + while True: + if self.get_has_current_context(): + print("Wait for data...", file=sys.stderr) + has_error: bool = False + try: + with open(self._file_data) as if_data: + data: str = if_data.readline().strip() + except: + has_error = True + + if not has_error: + print("\tReceived data:",data, file=sys.stderr) + + print("Do stuff...", file=sys.stderr) + result: str = self._current_context + ":" + data + print("\tdone", file=sys.stderr) + + print("Output...", file=sys.stderr) + with open(self._out, 'w') as out: + out.write(result) + + print("\tdone", file=sys.stderr) + +def is_named_pipe_fifo(filename: str): + st = os.stat(filename) + return stat.S_ISFIFO(st.st_mode) + +if __name__ == "__main__": + + assert(len(sys.argv) == 4) + + for i in range(1,4): + if not is_named_pipe_fifo(sys.argv[i]): + print("ERROR:", sys.argv[i], "is not a named pipe FIFO", file=sys.stderr) + sys.exit(ERROR.NO_FIFO) + + print("Start server", file=sys.stderr, flush=True) + server = Service(sys.argv[1], sys.argv[2], sys.argv[3]) + + do_current_context = threading.Thread( target = server.update_current_context ) + do_tasks = threading.Thread( target = server.handle_data ) + + do_current_context.start() + do_tasks.start() + + do_current_context.join() + do_tasks.join() + + print("End", file=sys.stderr) +