I am trying to develop a "reverse proxy" type of thing, and I am almost done. However, I have ran into an issue. I have two servers. The main server running the actual source (server A) and the proxy server which clients will connect to (server B).
Basically, my design is as follows:
After client connects to Server B (the proxy server)...
Client begins sending their packets to Server B.
Server B sends their packets to Server A
Server A receives their packet, parses it appropriately (this is the actual source), and then sends response packet(s) back to Server B
Server B receives the packet response from Server A, and sends it back to the Client.
So far, it is working fine. However, there is one major issue. The asynchronous loop can only receive one packet from Server A (the source server) per client packet. So if Server B sends one packet to Server A, Server B will only be able to receive one response packet from Server A, even if Server A wants to respond with multiple packets. I believe the read function is blocking.
I have a few files. I am running everything inside this loop:
import asyncio
asyncio.run(server.loop())
And then this is the actual loop (it is a longer file, but I removed redundant information):
async def loop(self):
self.server = await asyncio.start_server(self.init, self.settings['proxy']['host'], self.settings['proxy']['ports'][self.type])
async with self.server:
print("~Listening # {%s:%s}" % (self.settings['proxy']['host'], str(self.settings['proxy']['ports'][self.type])))
print()
await self.server.start_serving()
await self.server.serve_forever()
async def init(self, r, w):
await cli.client(self, r, w).connect()
As you can see it calls a client class, that client class contains the loop for the actual proxy file. It receives packets from the client, sends it to Server A, receives Server A's response, and sends it back to the client. This is the client class (redundant information removed)
async def connect(self):
self.ip = self.w.get_extra_info('peername')
self.tag = ("[%s:%s]" % (self.ip[0], str(self.ip[1])))
self.dest['r'], self.dest['w'] = await asyncio.open_connection(host=self.host, port=self.port)
print("NEW_CLIENT::%s" % (self.tag))
while(not self.w.is_closing()):
try:
data = await self.r.readuntil(separator=b'\x00')
if data:
await self.forward(data)
else:
await self.disconnect()
except asyncio.IncompleteReadError:
await self.disconnect()
async def forward(self, data):
#data = data.decode("utf-8")[:-1]
print("<= SEND_DEST::%s %s" % (self.tag, data.decode("utf-8")[:-1]))
self.dest['w'].write(data)
await self.dest['w'].drain()
data = await self.dest['r'].readuntil(separator=b'\x00')
print("=> RECV_DEST::%s => %s" % (self.tag, data.decode("utf-8")[:-1]))
if data:
self.w.write(data)
async def disconnect(self):
print("[Disconnect Client]: %s:%s" % (self.ip[0], str(self.ip[1])))
self.w.close()
So far, everything works as intended. The client sends a packet to this proxy file (server B), the packet is sent to the main source (server A), the main source sends back packets to the proxy file (server B), and then finally server B sends server A's response back to the client. It is a working proxy script.
The Problem: With this reverse proxy, I can only receive one line at a time from the main Server A. So basically, if the client sends Server B 1 packet, this remote proxy only reads 1 response packet from Server A, even if Server A sends several response packets back. So basically, I can only read one line at a time, in the forward() function.
I can't really figure out what to do here, except maybe make another loop for the reading process? I don't know how I would do that though. Maybe use run_in_executor?
Related
Help me figure out how to implement it correctly.
The bottom line: The client is charging stations that connect, open a socket and send messages to the server and receive responses.
Server - listens to the port, sees the connected station, receives messages and sends responses to them.
Question: When the client connects and sends headers, I can send a message to the client. But I need to periodically send messages to the client that keeps the socket open, I don't understand how to implement this. Can someone tell me?
sample sending code:
charge_point_id = path.strip('/')
cp = client_main(charge_point_id, websocket)
logging.info(charge_point_id)
print(charge_point_id)
print(path)
await websocket.send(json.dumps([2,"222", "GetLocalListVersion", {}]))
await cp.start()
example of receiving a message from a client:
class client_main(cp):
errors = False
if not errors:
#on('BootNotification')
def on_boot_notitication(self, charge_point_vendor, charge_point_model,charge_point_serial_number,firmware_version,
meter_type, **kwargs):
return call_result.BootNotificationPayload(
status="Accepted",
current_time=date_str.replace('+00:00','Z'),
interval=60
)
in this case, the charging station according to the ocpp protocol opens the connection and keeps it open, it should be possible to somehow write to him
how do i send a message to the client? My example:
#on('Heartbeat')
def on_getlocallistversion(self):
await self.route_message(json.dumps([2,"222","GetLocalListVersion",{}]))
def on_hearbeat(self):
return call_result.HeartbeatPayload(
current_time=datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S')+"Z"
)
I get an error:
await self.route_message(json.dumps([2,"222",
"GetLocalListVersion",{}]))
This is impossible to do. You can send a message only when the client is connected.
The title explain everything. I made Java code that sends messgaes for the python server, but evry time, just the first message is sends because every time, java conneced again to server, and the server keeps waiting to next message from the first client that I send in the first time.
How can the server get message from all clients are connectd? and not just from one?
My python server:
server = socket.socket()
server.bind((socket.gethostname(), 4786))
server.listen(5)
(client, (ipNum, portNum)) = server.accept()
print("Client connected")
while True:
message = str(client.recv(64).decode()) # Check if client send message. I want to change it to check all clients
if(message != ""):
print("Client: " + message)
else:
time.sleep(0.1)
Summary
The server.accept() must be called inside the loop.
TLDR
The socket server returned from the call socket.socket() is a 'listening' socket. It is not used for any data transfer but just for listening incoming connections. When the server is willing to accept incoming connection then calls server.accept(). This call waits till a client connects. When a client connects the accept wakes up and returns a socket that represents a connection to one client. This socket is then used for data send and received and should be closed when the communication with this specific client is done.
When server wants to accept connection from another client it must call server.accept() again to wait for connection from another client and use the unique client socket for each connected client.
If it sufficient to handle client sequentially then you can just move the call accept onto the loop. Furthermore you should close the client socket when the communication with the client is done.
If multiple clients can be connected in parallel then slightly more complicated design is needed. You can start a new thread for each client after accepting the connection. The thread can call recv in a loop and terminates when the client disconnects. See Multi Threaded TCP server in Python for example.
I have Python client which opens a websocket connection to a server and subscribes to particular topic using STOMP protocol, subscription goes just fine as i see on the server all is fine. However, When the server publishes a few messages the client does not receive any.
Here are the codes used:
Client
# coding: utf-8
import websocket
import stomp
import stomper
token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsInByaW5jaXBhbF9uYW1lIjoiYWRtaW4iLCJpc3MiOiJBdGhlbmEiLCJ1c2VydHlwZSI6IkxPQ0FMIiwiYW9zX3ZlcnNpb24iOiJldXBocmF0ZXMtNS4xMS1zdGFibGUiLCJyZWdpb24iOiJlbi1VUyIsImV4cCI6MTczNDI4MDI3NywidXVpZCI6ImI4MzhjOGRkLWI4NmQtNGNkZS05ZTE4LTUxM2E1OTk4ODhhYyIsImlhdCI6MTU3NjYwMDI3NywiYXV0aG9yaXRpZXMiOiJST0xFX0NMVVNURVJfQURNSU4sUk9MRV9NVUxUSUNMVVNURVJfQURNSU4sUk9MRV9VU0VSX0FETUlOLFJPTEVfQ0xVU1RFUl9WSUVXRVIiLCJqdGkiOiI1NTU1ZjEwZC04NGQ5LTRkZGYtOThhNC1mZmI1OTM1ZTQwZWEifQ.LOMX6ppkcSBBS_UwW9Qo2ieWZAGrKqADQL6ZQuTi2oieYa_LzykNiGMWMYXY-uw40bixDcE-aVWyrIEZQbVsvA"
headers = {"Authorization": "Bearer " + token}
uri = "ws://127.0.0.1:8765/notifications/websocket"
def on_msg(ws, msg):
print(msg)
def on_error(ws, err):
print(err)
def on_closed(ws):
print("#Closed#")
def on_open(ws):
sub = stomper.subscribe("/user/queue/alert", "MyuniqueId", ack="auto")
ws.send(sub)
headers = {"Authorization": "Bearer " + token}
websocket.enableTrace(True)
ws = websocket.WebSocketApp(uri, header=headers, on_message=on_msg, on_error=on_error, on_close=on_closed)
ws.on_open = on_open
ws.run_forever()
Code server uses to publish the message:
for (WatchesSubscription s : subscriptions) {
template.convertAndSendToUser(s.getSession().getUser(), destination, dto);
}
When i checked out the value of the above variables i saw that destination was as expected queue/alerts. I have java client to test out as well and it works just fine. I have even tried this by subscribing to /topic/alerts and sending to it via template.convertAndSend(/topic/alerts), here too i received nothing. I am a drawing a complete blank on this and would appreciate any sort of help!
After many days of hair pulling I finally figured out the reason and the fix!
The java client I used was
WebSocketStompClient stompClient = new WebSocketStompClient(transport);.The stompClient.connect(URL, webSocketHttpHeaders, sessionHandler); method implicitly sends a stomp CONNECT\n\n\x00\n
The Springboot server which has been configured for STOMP understands this as a connection request and responds with a CONNECT_ACK.
When this ACK is sent it also updates it's local UserRegistry with the new user. So the internal message broker knows that there is a user who has subscribed to so-and-so topic.
In my Python code, i had merely opened a Websocket connection and after that directly sent a SUBSCRIBE message. So the broker never got a CONNECT so the user was never stored! This resulted in the messages later on being published to be merely discarded by the broker.
The fix was to send a CONNECT\n\n\x00\n after opening up the connection and before the subscription. Here is the code:
def on_open(ws):
#The magic happens here!
ws.send("CONNECT\naccept-version:1.0,1.1,2.0\n\n\x00\n")
sub = stomper.subscribe("/user/queue/alert", "MyuniqueId", ack="auto")
ws.send(sub)
This is probably very simple socket message exchange programming, but with my flimsy idea on socket and all, I could not really make it work properly so I'm reaching out for help.
Scenario : Two clients send messages. First one sends Halo and Seeya. After the first sends those messages, the second sends Hello and Bye (This client will just time sleep 6 secs to keep this order). To all messages, server replies with (original msg), client (number)!and a reply message is broadcasted to both clients.
So ideally, the result on both clients would look like this :
Halo, client 1!
Seeya, client 1!
Hello, client 2!
Bye, client 2 !
I couldn't make it to numbering each client, but here is my code that works weird.
server
import socket
clients = []
# send msgs to every client
def broadcast(message):
for client in clients :
client.send(message)
# connection part
s = socket.socket()
s.bind(('127.0.0.1', 7070))
s.listen(2)
while True:
c, addr = s.accept()
if c:
clients.append(c)
msg = c.recv(1024).decode()
msg += ', client!'
broadcast(msg.encode())
client1
### here goes connection part ###
s.send(("Halo").encode())
print(f"server = {(s.recv(1024)).decode()}")
# I've tried adding socket closing/connection part here
s.send(("Seeya").encode())
print((s.recv(1024)).decode())
time.sleep(3)
s.close()
client2 - first connected, but waits 6 secs for message order
### here goes connection part ###
time.sleep(6) # waits for message order
s.send(("Hello").encode())
print(f"server = {(s.recv(1024)).decode()}")
# I've tried adding socket closing/connection part here
s.send(("Bye").encode())
print((s.recv(1024)).decode())
time.sleep(3)
s.close()
The result I get is...
# On client 1 side
Halo, client! # no Seeya, Hello, Bye
# connection isn't closed
# On client 2 side
Hello, client! # no Seeya, Bye
Halo, client! # connection is closed
You have several issues going on here. The first and primary one is that your server's main loop is messed up. Each time through the loop, your server wants to accept a connection. So, the first client to connect gets accepted and immediately its first message is received. But the other client hasn't yet been accepted and so will not receive this first message. Then the second client connection is accepted and its first message is then sent to both clients, but then the loop iterates again and no more messages will be sent from the server until a third client connects. Etc.
So you need to separate accepting connections and receiving messages. This can be done in several ways. The most straight-forward way is to use the select function to wait on a number of sockets at once. That is, if you have a list of sockets including the listening socket and previously accepted ones, you'd do something like this:
# List starts with only listening socket
list_of_sockets = [lsock]
...
while True:
# Wait until some socket becomes "readable"
rfds, _wfds, _xfds = select.select(list_of_socks, [], [])
for sock in rfds:
if sock is lsock:
# Listening socket ready. Accept new connection
c, addr = lsock.accept()
print(f"Accepted {c}")
list_of_socks.append(c)
else:
msg = sock.recv(1024)
if msg:
# Received data from connected socket. Send to all
print(f"Got {msg.decode()} from {sock}")
broadcast(msg)
else:
# Got end of file (this client closed). Remove client from list
print(f"Closed {sock}")
list_of_socks.remove(sock)
Another issue with your code that will not be addressed by the server code above: You cannot assume that each message you send will be received as a distinct unit. That is, if the server sends 'Halo' and then it sends 'Hello' before you (a client) have done a recv, then in all likelihood, all the data will be returned in one fell swoop; that is 'HaloHello'.
Generally therefore you will want to put some kind of separator in the data (like a newline [\n] -- but then you'll need to parse the received data) or, better yet, place a fixed-length field in front of each message, giving the length of the subsequent variable-length part, so that you can receive and process exactly one message at a time. (In python, this typically involves using the struct module's pack and unpack functions.) As a result, your current client code will probably not properly sequence messages as you wish.
Also -- though it is less likely to cause a problem -- the same goes for send: you should not assume that send(N) sends exactly N bytes. It might send 1, 2, 3 or N-1 bytes. You can use sendall to ensure that all bytes are sent.
I am attempting to write a simple TCP server in twisted which has to perform the following operations in sequence:
A client connects to the server and the KEEPALIVE flag for this connection is set to 1.
The server receives data from the client.
It then computes the response which is a list.
The server then sends each item of the list one by one while waiting for explicit ACKs from the client in between, i.e., after sending a single item from the list, the server waits for an ACK packet from the client and only after receiving the ACK does it proceed to send the rest of the items in the same manner.
The following is the code:
class MyFactory(ServerFactory):
protocol = MyProtocol
def __init__(self, service):
self.service = service
class MyProtocol(Protocol):
def connectionMade(self):
try:
self.transport.setTcpKeepAlive(1)
except AttributeError:
pass
self.deferred = Deferred()
self.deferred.addCallback(self.factory.service.compute_response)
self.deferred.addCallback(self.send_response)
def dataReceived(self, data):
self.fire(data)
def fire(self, data):
if self.deferred is not None:
d, self.deferred = self.deferred, None
d.callback(data)
def send_response(self, data):
for item in data:
d = Deferred()
d.addCallback(self.transport.write)
d.addCallback(self.wait_for_ack)
d.callback(item)
return
def wait_for_ack(self, dummy):
try:
self.transport.socket.recv(1024)
except socket.error as e:
print e
return
Upon running the server and the client I get the following exception:
Resource temporarily unavailable
I understand the reason for this exception - I'm trying to call a blocking method on non blocking socket.
Please help me in finding a solution to this problem.
There are some problems with your example:
You don't define compute_response anywhere (among other things) so I can't run your example. Consider making it an http://sscce.org
You should never call either send or recv on a socket underlying a Twisted transport; let Twisted call those methods for you. In the case of recv it will deliver the results of recv to dataReceived.
You can't rely upon dataReceived to receive whole messages; packets may always be arbitrarily fragmented in transit so you need to have a framing protocol for encapsulating your messages.
However, since my other answer was so badly botched, I owe you a more thorough explanation of how to set up what you want to do.
As stipulated in your question, your protocol is not completely defined enough to give an answer; you cannot do requests and responses with raw TCP fragments, because your application can't know where they start and end (see point 3 above). So, I've invented a little protocol to serve for this example: it's a line-delimited protocol where the client sends "request foo\n" and the server immediately sends "thinking...\n", computes a response, then sends "response foo\n" and waits for the client to send "ok"; in response, the server will either send the next "response ..." line, or a "done\n" line indicating that it's finished sending responses.
With that as our protocol, I believe the key element of your question is that you cannot "wait for acknowledgement", or for that matter, anything else, in Twisted. What you need to do is implement something along the lines of "when an acknowledgement is received...".
Therefore, when a message is received, we need to identify the type of the message: acknowledgement or request?
if it's a request, we need to compute a response; when the response is finished being computed, we need to enqueue all the elements of the response and send the first one.
if it's an acknowledgement, we need to examine the outgoing queue of responses, and if it has any contents, send the first element of it; otherwise, send "done".
Here's a full, runnable example that implements the protocol I described in that way:
from twisted.internet.protocol import ServerFactory
from twisted.internet.task import deferLater
from twisted.internet import reactor
from twisted.internet.interfaces import ITCPTransport
from twisted.protocols.basic import LineReceiver
class MyProtocol(LineReceiver):
delimiter = "\n"
def connectionMade(self):
if ITCPTransport.providedBy(self.transport):
self.transport.setTcpKeepAlive(1)
self.pendingResponses = []
def lineReceived(self, line):
split = line.rstrip("\r").split(None, 1)
command = split[0]
if command == b"request":
# requesting a computed response
payload = split[1]
self.sendLine("thinking...")
(self.factory.service.computeResponse(payload)
.addCallback(self.sendResponses))
elif command == b"ok":
# acknowledging a response; send the next response
if self.pendingResponses:
self.sendOneResponse()
else:
self.sendLine(b"done")
def sendOneResponse(self):
self.sendLine(b"response " + self.pendingResponses.pop(0))
def sendResponses(self, listOfResponses):
self.pendingResponses.extend(listOfResponses)
self.sendOneResponse()
class MyFactory(ServerFactory):
protocol = MyProtocol
def __init__(self, service):
self.service = service
class MyService(object):
def computeResponse(self, request):
return deferLater(
reactor, 1.0,
lambda: [request + b" 1", request + b" 2", request + b" 3"]
)
from twisted.internet.endpoints import StandardIOEndpoint
endpoint = StandardIOEndpoint(reactor)
endpoint.listen(MyFactory(MyService()))
reactor.run()
I've made this runnable on standard I/O so that you can just run it and type into it to get a feel how it works; if you want to run it on an actual network port, just substitute that with a different type of endpoint. Hopefully this answers your question.