Compare commits
3 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c58b426876 | |||
| 4b42f79590 | |||
| 016852e281 |
3 changed files with 156 additions and 13 deletions
55
README.md
55
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*.
|
Nowadays, you may have eared of their latest avatar: *web services*.
|
||||||
As our programmer is going to realize, one now have *two* problems.
|
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.
|
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,
|
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.
|
—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.
|
But most of the time, the actual problem does not really involve these situations.
|
||||||
At least not at the beginning (which means probably never).
|
At least not at the beginning (… which means probably never).
|
||||||
If you are building up (firsts versions of) communicating programs
|
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,
|
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:
|
then I have good news:
|
||||||
**you don't have to use web services** (or any kind of middleware).
|
**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
|
### Overview
|
||||||
|
|
@ -49,7 +55,7 @@ in which you read/write.
|
||||||
|
|
||||||
Once you made your service on top of named pipes,
|
Once you made your service on top of named pipes,
|
||||||
it is easy to wrap it within an interface made with other languages/tools.
|
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
|
Be warned that this is not secure, though, you should only use this for testing
|
||||||
purpose in a secured local network.
|
purpose in a secured local network.
|
||||||
|
|
@ -67,13 +73,13 @@ The theoretical principle can be represented by this UML sequence diagram:
|
||||||
│ │ │ │
|
│ │ │ │
|
||||||
│ │ │┌──────╢
|
│ │ │┌──────╢
|
||||||
│ │ block││ wait ║
|
│ │ block││ wait ║
|
||||||
│ask │ │└─────→║
|
│ask │ │└─────>║
|
||||||
├─────────────→│ │
|
├─────────────>│ │
|
||||||
╟─────┐│ ├──────→│
|
╟─────┐│ ├──────>│
|
||||||
║wait ││block │ ║process
|
║wait ││block │ ║process
|
||||||
║←────┘│ │ ║
|
║<────┘│ │ ║
|
||||||
│ │←──────────────┤
|
│ │<──────────────┤
|
||||||
│←─────┤ │ tell│
|
│<─────┤ │ tell│
|
||||||
│ │ │ │
|
│ │ │ │
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -84,6 +90,29 @@ Notes:
|
||||||
for the sake of simplicity, but you may just as well use only one.
|
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
|
Build and run
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
|
|
||||||
21
service1.py
Executable file
21
service1.py
Executable file
|
|
@ -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)
|
||||||
93
service2.py
Executable file
93
service2.py
Executable file
|
|
@ -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)
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue