# -*- coding: utf-8 -*- """ Created on Sun Mar 22 10:46:20 2020 @author: cpan Simple client implementation with timeout feature to prevent hanging """ import sys import socket import json import io import struct def create_request(action, value): if action == "search": return dict( type="text/json", encoding="utf-8", content=dict(action=action, value=value), ) else: return dict( type="binary/custom-client-binary-type", encoding="binary", content=bytes(action + value, encoding="utf-8"), ) def _json_encode(obj, encoding): return json.dumps(obj, ensure_ascii=False).encode(encoding) def _json_decode(json_bytes, encoding): tiow = io.TextIOWrapper( io.BytesIO(json_bytes), encoding=encoding, newline="" ) obj = json.load(tiow) tiow.close() return obj def _create_message(*, content_bytes, content_type, content_encoding): jsonheader = { "byteorder": sys.byteorder, "content-type": content_type, "content-encoding": content_encoding, "content-length": len(content_bytes), } jsonheader_bytes = _json_encode(jsonheader, "utf-8") message_hdr = struct.pack(">H", len(jsonheader_bytes)) message = message_hdr + jsonheader_bytes + content_bytes return message def create_message(request): content = request["content"] content_type = request["type"] content_encoding = request["encoding"] if content_type == "text/json": req = { "content_bytes": _json_encode(content, content_encoding), "content_type": content_type, "content_encoding": content_encoding, } else: req = { "content_bytes": content, "content_type": content_type, "content_encoding": content_encoding, } message = _create_message(**req) return message def main(): if len(sys.argv) != 5: print("usage:", sys.argv[0], " ") sys.exit(1) host, port = sys.argv[1], int(sys.argv[2]) action, value = sys.argv[3], sys.argv[4] request = create_request(action, value) message = create_message(request) addr = (host, port) print("starting connection to", addr) sock = socket.create_connection(addr) sock.settimeout(5) print(f"sending request {repr(message)} to {addr}") sock.sendall(message) _recv_buffer = bytearray(b"") def read_to_buffer(sock, length, buf : bytearray): while len(buf) < length: data = sock.recv(4096) buf += data # read and process protoheader (fixed length of 2 byte in network order) hdrlen = 2 read_to_buffer(sock, hdrlen, _recv_buffer) # process_protoheader() _jsonheader_len = struct.unpack(">H", _recv_buffer[:hdrlen])[0] _recv_buffer = _recv_buffer[hdrlen:] # read and process jsonheader read_to_buffer(sock, _jsonheader_len, _recv_buffer) # process_jsonheader() jsonheader = _json_decode(_recv_buffer[:_jsonheader_len], "utf-8") _recv_buffer = _recv_buffer[_jsonheader_len:] for reqhdr in ( "byteorder", "content-length", "content-type", "content-encoding", ): if reqhdr not in jsonheader: raise ValueError(f'Missing required header "{reqhdr}".') # read and process response content_len = jsonheader["content-length"] read_to_buffer(sock, content_len, _recv_buffer) # process_response(): data = _recv_buffer[:content_len] _recv_buffer = _recv_buffer[content_len:] if jsonheader["content-type"] == "text/json": encoding = jsonheader["content-encoding"] response = _json_decode(data, encoding) print("received response", repr(response), "from", addr) content = response result = content.get("result") print(f"got result: {result}") else: # Binary or unknown content-type response = data print( f'received {jsonheader["content-type"]} response from', addr, ) content = response print(f"got response: {repr(content)}") sock.shutdown(socket.SHUT_RDWR) sock.close() if __name__ == '__main__': main()