TCP-Sockets mit CORS-Header

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
Fipsi
User
Beiträge: 21
Registriert: Dienstag 2. Juli 2024, 04:14

Guten Morgen zusammen,

ich kämpft momentan mit einem CORS-Problem bei Sockets. Die Umgebung ist folgende:
Ich hab mehrere Displays die von Raspberrys gesteuert werden. Diese Raspberrys haben eine TCP Socket-Verbindung (mit dem 'socket'-Modul) zu einem zentralen Server (ebenfalls ein Raspi). Auf dem Server laufen in einem Pythonscript die Abläufe, die angeben, was die Displays anzeigen sollen. Das funktioniert soweit auch schon bereits.
Jetzt brauch ich noch einen weiteren Socket Client, der die Abläufe auf dem Server steuert. Als Frontend nutz ich dazu Angular, also eine Browseranwendung. Da die Angular-App aber sowohl auf dem Raspi liegen als auch auf einem anderem Server liegen kann, kommen die CORS-Header ins Spiel, da fängt der "Spaß" dann so richtig an. Ich hab bisher noch keine Möglichkeit gefunden, wie ich die CORS-Header für die Sockets konfigurieren kann. Stattdessen hab ich als Alternative die Lib Socket.io gefunden und damit auch experimentiert. Da hab ich aber das Problem, dass ich die Ports nicht konfigurieren kann. Da später selektiv mehrere offene Sockets/Prozesse offen sein können, will ich die über die Ports "aufteilen". Und da einer der Prozesse zeitkritisch ist will ich den auf jeden Fall als eigenen Dienst haben.

Kann mir hier jemand weiter helfen? Mir wär am liebsten, wenn ich dem bestehenden Socket-Server die CORS-Header hinzufügen könnte, dann müsst ich das bestehende nicht alles neu machen..

Vielen Dank im voraus und
liebe Grüße

Fipsi

(P.S.: Ich hab das Thema auch im Raspberry Forum (https://forum-raspberrypi.de/forum/thre ... rs-header/) gebracht, aber bisher noch keine Reaktion bekommen, deswegen wollt ich jetzt mein Glück hier versuchen).
Sirius3
User
Beiträge: 18250
Registriert: Sonntag 21. Oktober 2012, 17:20

Da du hier ziemlich viele Begriffe durcheinander bringst, ist es schwierig, Dir zu folgen. Du hast keine TCP-Sockets, sondern Du hast eine HTTP-Verbindung, über die Du noch zusätzlich über WebSockets zugreifen willst. Anscheinend möchtest Du einen Webserver inklusive WebSockets betreiben.
In Deinem Cross-Post erzeugst Du ja einen Webserver inklusive der Angabe eines Ports. Ein Server hat immer einen Port, egal wieviele Verbindungen offen sind. Was möchtest Du "aufteilen"?
Fipsi
User
Beiträge: 21
Registriert: Dienstag 2. Juli 2024, 04:14

Öhm... okay.. dann hab ich, glaub ich, gewaltig nen Wurm drin. Also folgendes zu meinem (geglaubten, offensichtlichem) Halbwissen:
Das Pi-Rudel verbindet sich mit dem Server über Sockets auf dem Port 14400. Das hielt ich für TCP-Sockets, die KEIN HTTP-Request sind.

Code: Alles auswählen

import socket

class Sockets():

    def __init__(self):
        self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        
        self.server.setblocking(0)

        self.server_adress = ('', 14400)
        self.server.bind(self.server_adress)
        print("Socket-Server open at " + self.server_adress[0] + ":" + str(self.server_adress[1]))

        self.server.listen(20)
Dann gibt's ja noch Librarys wie websockets, was dann wirklich websockets via HTTP-Requests sind.
Und die Lib socket.io gibt's auch, die hab ich so verstanden, dass das unter der Haube Websockets sind, aber wegen vielem Metadaten-Zeug nicht mit "normalen" websockets verwendet werden kann.

Jetzt brauch ich Zugriff aus dem Browser (Angular) in das Python-Script, in dem der Socket-Ausschnitt läuft. Angular kann sich auch dahin verbinden und Python meldet mir einen neuen Client, aber dann mischt sich der Browser ein und jammert wegen fehlenden CORS-Headern.

Ich hab auch ein Testscript für socket.io:

Code: Alles auswählen

from aiohttp import web
import socketio
import eventlet

sio = socketio.AsyncServer(cors_allowed_origins = [
    "http://localhost:4200"
    ])
app = web.Application()
sio.attach(app)

async def index(request):
    """Serve the client-side application."""
    #with open('index.html') as f:
    #    return web.Response(text=f.read(), content_type='text/html')

@sio.event
def connect(sid, environ):
    print("connect ", sid)

@sio.event
async def chat_message(sid, data):
    print("message ", data)

@sio.event
def disconnect(sid):
    print('disconnect ', sid)

#app.router.add_static('/static', 'static')
#app.router.add_get('/', index)

if __name__ == '__main__':
    web.run_app(app)
Damit kann sich Angular verbinden, aber ich bin auf den Port 8080 festgenagelt.

Ich hoffe, es ist jetzt verständlicher und ich hab da nicht nur falschen Blödsinn im Kopf. :cry:

Vielen Dank und
liebe Grüße

Fipsi
Sirius3
User
Beiträge: 18250
Registriert: Sonntag 21. Oktober 2012, 17:20

Dann lies doch mal die Dokumentation zu aiohttp. Natürlich kann man einen beliebigen Port angeben: `run_app(app, port=12345)`.
Und hier hast Du einen Webserver. Ob es irgendwo anders noch andere TCP-Verbindungen gibt, ist für dieses Problem völlig unerheblich.
Wie sieht Dein Setup genau aus? Was sind das denn für TCP-Sockets? Normalerweise möchte man nicht auf low-level-Ebene arbeiten, sondern auf ein bereits bestehendes Protokoll aufbauen. Z.B. irgendein Message-Queue-System, oder HTTP-Rest für ein Client-Server-Setup, oder ...
Fipsi
User
Beiträge: 21
Registriert: Dienstag 2. Juli 2024, 04:14

Oh man :roll: ich hab mich so auf des socket.io versteift den Port einzurichten, dass ich an des andre gar nicht gedacht hab. Danke dafür :geek:

Dass die socket.io anderen TCP-Verbindungen nicht in die Quere kommt ist mir bewusst, darum geht's mir auch gar nicht. Ich will eigentlich auf den bestehenden Sockets aufbauen, sodass ich nicht zwei Socket-Dienste in eine Anwendung pack und dann zwei Sockets "bearbeiten" muss, sondern dass alles über ein Socket-Dienst läuft.

Mein Setup ist eher Marke Eigenbau. In der Klasse, die ich vorhin gepostet hab sind noch Methoden für die Clientverwaltung sowie empfangen, verarbeiten und senden von Nachrichten im JSON-Format und das war's. Die run-Methode sieht folgend aus:

Code: Alles auswählen

def run(self):
        readable, writeable, exceptional = select.select(self.inputs, self.outputs, self.inputs)

        for s in readable:
            if s is self.server:
                connection, client_adress = s.accept()

                connection.setblocking(0)
                self.inputs.append(connection)
                self.connections[connection.getpeername()[1]] = connection
                self.new_connections.append(connection.getpeername()[1])
                self.message_queues[connection.getpeername()[1]] = queue.Queue()
                self.msg_recv[connection.getpeername()[1]] = b""
                self.msg_check_recv[connection.getpeername()[1]] = []

            else:
                try:
                    data = s.recv(1024)
                except:
                    print("Break Connection while reciving")
                    data = False

                if data:
                    #print(data)
                    if s.getpeername()[1] not in self.msg_recv:
                        self.msg_recv[s.getpeername()[1]] = b""

                    self.msg_recv[s.getpeername()[1]] += data

                    if s not in self.outputs:
                        self.outputs.append(s)

                else:
                    try:
                        self.del_connection(s.getpeername()[1], s)
                    except:
                        pass

        for s in writeable:
            try:
                #pass
                next_msg = self.message_queues[s.getpeername()[1]].get_nowait()
                s.sendall(bytes(json.dumps(next_msg) + "\n", encoding="utf-8"))
            except queue.Empty:
                pass
                #print("Queue Empty")
                #break;
            except OSError:
                continue

            else:
                pass

        for s in exceptional:
            self.del_connection(s.getpeername(), s)

        self.check_recv_msg()
Liebe Grüße

Fipsi
Benutzeravatar
grubenfox
User
Beiträge: 601
Registriert: Freitag 2. Dezember 2022, 15:49

zum Thema aiohttp: der Abschnitt https://docs.aiohttp.org/en/stable/thir ... extensions könnte da auch von Interesse sein
Sirius3
User
Beiträge: 18250
Registriert: Sonntag 21. Oktober 2012, 17:20

Nackte except darf man nicht verwenden, da damit auch Programmierfehler abgefangen werden könnten.
Variablennamen sollten sprechend sein, ein `s` ist das sicher nicht.
`connection.getpeername()[1]` sollte man nicht sechs mal aufrufen, zudem ist der peername ja nicht eindeutig. Warum nimmst Du nicht einfach den Socket?
Sechs parallele Datenstrukturen sollte es nicht geben, sondern alles zu einem Socket sollte in einer Datenstruktur gesammelt sein.
Eine Variable sollte immer nur Werte eines Typs haben, `data` ist aber mal vom Typ bytes und mal bool.
Ein Socket sollte nur in outputs stehen, wenn es auch tatsächlich Output gibt. Du fügst das zu früh zu.
`sendall` widerspricht select, das mußt Du in send umschreiben, und Dir merken, wie viel Du gesendet hast.
`continue` sollte man vermeiden, in Deinem Fall ist es total unnötig. Ein `else: pass`-Block ist unsinnig und kann weg.
Du scheinst ein Zeilenbasiertes Protokoll zu verwenden. Ist das korrekt?

Du hast immer noch nicht wirklich verraten, was da tatsächlich gemacht werden soll, client-server oder message-queueing. Für beides gibt es fertige Protokolle.
Fipsi
User
Beiträge: 21
Registriert: Dienstag 2. Juli 2024, 04:14

Danke für die Hinweise, ich werd die Stück für Stück abarbeiten.

Ist ne Weile her, dass ich des Protokoll geschrieben hab. Die Intention dahinter war, dass ich die Daten ins Format JSON pack, zu einem String umwandle (1 Zeile = 1 Nachricht), sende, der Empfänger wandelt es wieder zurück in JSON und verarbeitet das dann.

Ich glaub, das ist eher unter client-server einzuordnen: Das Pi-Rudel sind haben Displays und ein paar Relais. Der Server sagt dem Rudel, was angezeigt oder Relais geschaltet werden soll. Das Rudel kann ich noch in Bereiche unterteilen, also hab dann Rudel A, B, etc. Der Server schickt den Rudeln immer was sie anzeigen sollen und frag ab und zu auch ein paar Daten ab.
Das ist das, was schon funktioniert. Jetzt kommt noch ein Steuer-Client mit dazu, der dem Server sagt, was er an die Rudel weiter geben soll und erhält diese Daten aber auch, da in der Steuerung die Anzeige der Displays auch zu sehen ist.

Ich muss dazu sagen, ich mach das hobbymäßig, ich hab keinen professionellen Python-Hintergrund. Ich tu mir deshalb mit "fertigen Sachen" schwer, wenn ich nicht weiß, was im Hintergrund läuft oder wie ich manche Details an meine Anspräche anpass. Daher mach ich meistens viel selber und bin eher der Lerntyp "aufgabenorientiertes Lernen" statt "berieseln lassen und alles im Kopf haben". Daher sind auch Grundlagen bei mir rah gesät.

Vielen Dank und
liebe Grüße
Sirius3
User
Beiträge: 18250
Registriert: Sonntag 21. Oktober 2012, 17:20

Für mich hört sich das eher nach einem Messaging-Problem an, wenn der Server Befehle sendet und die Clients ihren Status zurückmelden. Da ist eine Standardlösung rabbitMQ. Natürlich muß man sich da erst einarbeiten, aber man hat dann eine Lösung, die auch funktioniert.
Beim Programmieren mit TCP kann man nämlich einiges falsch machen, und Randfälle, wie Verbindungsabbrüche sind noch nicht abgedeckt.
Fipsi
User
Beiträge: 21
Registriert: Dienstag 2. Juli 2024, 04:14

Okay, dann les ich mich da ein, danke für den Tipp.

Verbindungsabbrüche hab ich bei mir schon implementiert, alle 10 Sekunden gibt's eine "alive"-abfrage.

Liebe Grüße

Fipsi
Benutzeravatar
DeaD_EyE
User
Beiträge: 1217
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Für socketio gibt es eine Python-Bibliothek: https://python-socketio.readthedocs.io/en/stable/

Wenn man etwas Bestimmtes lernen möchte, ist nicht falsch dies selbst zu implementieren. Wenn es aber nur darum geht Daten zwischen Webserver und WebApp (die auf dem Client läuft) auszutauschen, sollte socketio eine der einfacheren Lösungen sein.

Ich hab zwar noch nie damit gearbeitet, aber das was hängen geblieben ist:
- Unterstützt Websockets und einen Fallback, falls der Browser das nicht unterstützt
- Hat eine Javascript-Bibliothek, die in die WebApp eingebaut wird
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Fipsi
User
Beiträge: 21
Registriert: Dienstag 2. Juli 2024, 04:14

Socket.io hatte ich in meinem 3 Post (viewtopic.php?p=429681&sid=13e18749f68e ... 8e#p429681) ja zum testen verwendet, die Lib ist mir also seit ein paar Tagen bekannt. Ich wollte aber vermeiden einen zweiten Socket-Dienst für das eine Teilprojekt zu verwenden. Es ist ja "nur" die Steuerung eine WebApp, deshalb wollt ich's über die TCP-Sockets versuchen.
Da ich jetzt aber offensichtlich nicht um eine Implementierung rum komm, wird's socket.io oder das von Sirius3 vorgeschlagene rabbitMQ, wenn ich mich dazu eingelesen hab.

Vielen Dank euch und
liebe Grüße

Fipsi
Benutzeravatar
grubenfox
User
Beiträge: 601
Registriert: Freitag 2. Dezember 2022, 15:49

Fipsi hat geschrieben: Dienstag 2. Juli 2024, 15:58 Da ich jetzt aber offensichtlich nicht um eine Implementierung rum komm, wird's socket.io oder das von Sirius3 vorgeschlagene rabbitMQ, wenn ich mich dazu eingelesen hab.
Wenn ich mir die Doku von socket.io so anschauen, dann scheint mir die korrekt Formulierung "wird's socket.io oder socket.io und das von Sirius3 vorgeschlagene rabbitMQ" zu sein... man kann socket.io ohne und auch mit Message Queue nutzen (https://python-socketio.readthedocs.io/ ... sage-queue) wobei 'Redis' vielleicht auch eine Alternative zu rabbitMQ sein könnte. (Hatte ich hier mal bei einem experimental-Projekt genutzt)
Fipsi
User
Beiträge: 21
Registriert: Dienstag 2. Juli 2024, 04:14

grubenfox hat geschrieben: Dienstag 2. Juli 2024, 16:29 dann scheint mir die korrekt Formulierung "wird's socket.io oder socket.io und das von Sirius3 vorgeschlagene rabbitMQ" zu sein...
Jepp, das hab ich mittlerweile auch gelesen.
Soweit ich rabbitMQ bisher verstanden hab, ist das nicht die optimale Lösung für meine Anwendung. Ich hab nach dem Getting started meine Anwendung jetzt mehr als Server-Client statt message-queueing eingeordnet, da der Server mehr macht als nur Nachrichten verteilen (hab ich vllt. nicht richtig erklärt, Entschuldigung).

Bevor ich jetzt aber richtig damit starte muss ich mich doch erst mal noch mal mit asyncio auseinander setzen.. das hab ich noch nicht so ganz in mein Schädel gebracht.

Liebe Grüße

Fipsi
Fipsi
User
Beiträge: 21
Registriert: Dienstag 2. Juli 2024, 04:14

Guten Morgen,

ich häng immer noch mit'm Verständnis zu asyncio fest, ich hoffe, ich könnt mir dabei noch mal helfen. :?: Ich hab dazu zwei Punkte:

Ich kann ja jede Funktion, die ich will mit async definieren. Nehmen wir als Beispiel eine Funktion, die eine Datei öffnen, was rein schreiben und wieder schließen soll. Die drei Aufgaben sind ja jeweils blockierend. Woher weiß Python das, erkennt es "ah, da muss ich auf was warten, ich schau mal nach anderen Aufgaben"? Und da kommen wir zum await, was für mich ein noch größeres Fragezeichen ist: Ich hab das soweit verstanden, dass man das await da platzieren kann, wo der reguläre Ablauf unterbrochen werden kann und Python dann schaut "hab ich blockierende Aufgaben, mit denen ich weiter arbeiten kann?" oder auch nur eine einzelne Funktion, die einer Variable zugeordnet wurde (aufgabe = await machWas()) weiter ausgeführt wird, falls die soweit ist.
Mein Gefühl sagt mir, dass ich auf dem komplett falschen Dampfer unterwegs bin, aber vllt. kann mich ja wer aufklären :geek:

Der zweite Punkt:
Brauch ich das bei meinem jetzigen Projekt überhaupt? Ich bau meinen Code sowieso nicht blockierend auf. Pseudecode:

Code: Alles auswählen

couter = 0
last_time = time.time()
messages_recv = []
messages_send = []

while True:
    if (last_time + 1000 < time.time()):
        counter += 1
        last_time += 1000

    if (len(messages_recv) > 0):
        machWasDraus(messages_recv)
    
    if (len(messages_send) > 0):
        socket.send(messages_send)
    
    if (counter >= 30):
        counter = 0
Wenn ich event-basierend Socket-Nachrichten empfang und in messages_recv park, welche dann wie im Code abgefragt werden, blockier ich ja nicht?
Ebenso wenn ich socket-Nachrichten raus schick dürfte ich ja auch nicht blockieren?
Und wenn die Funktionen die ich dann ausführ, nur einfache Operationen ohne z. B. Dateizugriff sind, dürfte ja auch nichts blockieren?
Sollte ich doch irgendwo was übersehen, dass etwas blockiert und in der Zeit Socket-Nachrichten rein kommen, dann dürften die ja auch nicht "verloren gehen", weil die in Buffer kommen und dann nur erst später gelesen werden?
Zu guter Letzt: Wenn ich kein asyncio brauch und eventbasiert die Sockets handhabe, dann brauch ich auch von socket.io den AsyncServer nicht, oder?

Zu viele Fragen zu dieser Uhrzeit :shock: :?

Vielen Dank schon mal und
liebe Grüße

Fipsi
Sirius3
User
Beiträge: 18250
Registriert: Sonntag 21. Oktober 2012, 17:20

Du hast oben ein kompliziertes Konstrukt mit Select gezeigt. Damit versuchst du mit vielen Verbindungen gleichzeitig zu jonglieren, dabei kann man leicht Fehler machen, wie ich bereits geschrieben habe, weil man z.B sendall benutzt, das blockierend ist.
Benutzt man dagegen ein high-level asynchrones messaging system, dann kann man sich ganz auf die Verarbeitung der Nachrichten konzentrieren, und muss sich nicht mit Sockets herumschlagen.
Ein weiterer Vorteil ist, dass man verschiedene asynchrone frameworks miteinander kombinieren kann, Pika fürs Messaging und Flask als Webserver.
Bei normaler Programmierung bringt jedes Framework seine eigene Event Schleife mit, und davon kann jeweils nur eine laufen.
Fipsi
User
Beiträge: 21
Registriert: Dienstag 2. Juli 2024, 04:14

Mit Sockets hatte ich mich auf socket.io bezogen, nicht auf das, was ich zuvor verwendet hab.
Ich versteh aber auch trotzdem was du mir sagen willst.

Vielen Dank und
liebe Grüße

Fipsi
Antworten