├── .gitignore ├── README.md ├── bin ├── JpgDll.dll ├── SavePhoto.dll ├── Sdtapi.dll ├── Sdtapi.lib ├── WltRS.dll └── dewlt.dll ├── demo.html ├── reader.py ├── sdk ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-37.pyc │ └── sdk.cpython-37.pyc └── sdk.py ├── snapshot.png └── websocket_server ├── __init__.py ├── __pycache__ ├── __init__.cpython-37.pyc └── websocket_server.cpython-37.pyc └── websocket_server.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python 语言开发的集成“身份证阅读器”和“打开摄像头”功能 附EXE可执行软件和源代码 2 | #另有golang语言和rust语言版本的源码,纯个人业余开发,可商用定制,欢迎交流 3 | #实现“在线身份证读取”和“在线拍照认证”功能 4 | 5 | # B/S框架 idcardReader 6 | 精伦iDR210,千景J15S,华视CVR-100U,华旭HX-FDX3S,神思SS628-100U,新中新DKQ-A16D,普天CP IDMR02/TG,神盾ICR-100U身份证阅读器Web版,提供web服务,基于HTTP协议 7 | 8 | # B/S框架 camera 9 | 打开本地摄像头,提供web服务,基于WebSocket协议 10 | 11 | # 快照 12 | ![Image text](https://raw.githubusercontent.com/zjw939057120/idcardReader-camera/master/snapshot.png) 13 | -------------------------------------------------------------------------------- /bin/JpgDll.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zjw939057120/idcardReader-camera/bcb3e5a8ad35c0a2365b80d865712a65aff851d7/bin/JpgDll.dll -------------------------------------------------------------------------------- /bin/SavePhoto.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zjw939057120/idcardReader-camera/bcb3e5a8ad35c0a2365b80d865712a65aff851d7/bin/SavePhoto.dll -------------------------------------------------------------------------------- /bin/Sdtapi.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zjw939057120/idcardReader-camera/bcb3e5a8ad35c0a2365b80d865712a65aff851d7/bin/Sdtapi.dll -------------------------------------------------------------------------------- /bin/Sdtapi.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zjw939057120/idcardReader-camera/bcb3e5a8ad35c0a2365b80d865712a65aff851d7/bin/Sdtapi.lib -------------------------------------------------------------------------------- /bin/WltRS.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zjw939057120/idcardReader-camera/bcb3e5a8ad35c0a2365b80d865712a65aff851d7/bin/WltRS.dll -------------------------------------------------------------------------------- /bin/dewlt.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zjw939057120/idcardReader-camera/bcb3e5a8ad35c0a2365b80d865712a65aff851d7/bin/dewlt.dll -------------------------------------------------------------------------------- /demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 身份证阅读器Web版 - Power by 明珠玮玉 5 | 82 | 119 | 120 | 121 |
122 |

123 | 身份证阅读器Web版 124 |

125 | 126 | 127 | 132 | 133 | 134 | 135 | 136 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 148 | 149 | 150 | 151 | 154 | 155 | 156 | 157 | 160 | 161 | 162 | 163 | 166 | 167 | 168 | 169 | 172 | 173 | 174 | 175 | 178 | 179 | 180 | 181 | 184 | 185 | 186 | 187 | 190 | 191 | 192 | 193 | 197 | 198 |
128 | 129 | 130 | 131 |
姓名: 137 | 138 |
性别: 146 | 147 |
民族: 152 | 153 |
出生: 158 | 159 |
地址: 164 | 165 |
身份证号: 170 | 171 |
签发机关: 176 | 177 |
开始期限: 182 | 183 |
结束期限: 188 | 189 |
返回数据: 194 | 196 |
199 |
200 | 201 | 202 | -------------------------------------------------------------------------------- /reader.py: -------------------------------------------------------------------------------- 1 | from websocket_server import WebsocketServer 2 | import sdk 3 | import json 4 | 5 | 6 | # 处理客户端消息 7 | def handle_message(message): 8 | if message == '--OpenReader': 9 | # 打开设备 10 | ret = sdk.API().init_comm() 11 | if ret == 1: 12 | sdk.Init.Initialization['comm'] = True 13 | elif message == '--ReadCard': 14 | # 读取身份证信息 15 | if not sdk.Init.Initialization['comm']: 16 | ret = sdk.API().init_comm() 17 | if ret['ret'] == 1: 18 | sdk.Init.Initialization['comm'] = True 19 | else: 20 | return ret 21 | # 卡认证 22 | sdk.API().authenticate() 23 | # 判断卡 24 | ret = sdk.API().card_on() 25 | if ret['ret'] != 1: 26 | return ret 27 | # 读取卡 28 | ret = sdk.API().read_base_infos() 29 | if ret['ret'] != 1: 30 | return ret 31 | elif message == '--CloseReader': 32 | # 关闭设备 33 | ret = sdk.API().close_comm() 34 | if ret == 1: 35 | sdk.Init.Initialization['comm'] = False 36 | else: 37 | ret = {'ret': 0, 'msg': '未知指令', 'data': ''} 38 | return ret 39 | 40 | 41 | 42 | # 客户端上线 43 | def new_client(client, server): 44 | pass 45 | ret = sdk.API().init_comm() 46 | if ret['ret'] == 1: 47 | sdk.Init.Initialization['comm'] = True 48 | print(ret) 49 | server.send_message(client, json.dumps(ret)) 50 | 51 | 52 | 53 | # 客户端离线 54 | def client_left(client, server): 55 | pass 56 | ret = sdk.API().close_comm() 57 | if ret['ret'] == 1: 58 | sdk.Init.Initialization['comm'] = False 59 | print(ret) 60 | 61 | 62 | # 接收客户端消息 63 | def message_received(client, server, message): 64 | pass 65 | if len(message) > 200: 66 | message = message[:200] + '..' 67 | received = handle_message(message) 68 | server.send_message(client, json.dumps(received)) 69 | 70 | 71 | PORT = 9000 72 | server = WebsocketServer(PORT) 73 | server.set_fn_new_client(new_client) 74 | server.set_fn_client_left(client_left) 75 | server.set_fn_message_received(message_received) 76 | server.run_forever() 77 | -------------------------------------------------------------------------------- /sdk/__init__.py: -------------------------------------------------------------------------------- 1 | from .sdk import * 2 | -------------------------------------------------------------------------------- /sdk/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zjw939057120/idcardReader-camera/bcb3e5a8ad35c0a2365b80d865712a65aff851d7/sdk/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /sdk/__pycache__/sdk.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zjw939057120/idcardReader-camera/bcb3e5a8ad35c0a2365b80d865712a65aff851d7/sdk/__pycache__/sdk.cpython-37.pyc -------------------------------------------------------------------------------- /sdk/sdk.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | import ctypes 3 | import base64 4 | import os 5 | import cv2 6 | 7 | 8 | class Init: 9 | # 初始化状态 10 | Initialization = { 11 | 'comm': False, 12 | } 13 | 14 | 15 | class API: 16 | try: 17 | dll = ctypes.windll.LoadLibrary("bin/Sdtapi.dll") 18 | except Exception as e: 19 | ctypes.windll.user32.MessageBoxA(0, str(e).encode('gbk'), '提示'.encode('gbk'), 0) 20 | os._exit(0) 21 | 22 | # 4.1.1.端口初始化函数 23 | def init_comm(self): 24 | # 根据实际情况选择USB口和串口循环次数 25 | # USB型iDR, 1001 - 1016 26 | for i in range(1001, 1006): 27 | ret = self.dll.InitComm(i) 28 | if ret == 1: 29 | break 30 | # 串口型iDR,1--16 31 | if ret != 1: 32 | for i in range(1, 16): 33 | ret = self.dll.InitComm(i) 34 | if ret == 1: 35 | break 36 | if ret == 1: 37 | return {'ret': 1, 'msg': '端口初始化成功', 'data': ''} 38 | else: 39 | return {'ret': 0, 'msg': '端口初始化失败', 'data': ''} 40 | 41 | # 4.1.2.端口关闭接口 42 | def close_comm(self): 43 | ret = self.dll.CloseComm() 44 | if ret == 1: 45 | return {'ret': 1, 'msg': '端口关闭成功', 'data': ''} 46 | elif ret == -1: 47 | return {'ret': 0, 'msg': '端口未打开', 'data': ''} 48 | else: 49 | return {'ret': 0, 'msg': '端口关闭失败', 'data': ''} 50 | 51 | # 4.2.1.卡认证接口 52 | def authenticate(self): 53 | ret = self.dll.Authenticate() 54 | if ret == 1: 55 | return {'ret': 1, 'msg': '卡认证成功', 'data': ''} 56 | else: 57 | return {'ret': 0, 'msg': '卡认证失败', 'data': ''} 58 | 59 | # 4.2.6.判断身份证是否在设备上 60 | def card_on(self): 61 | ret = self.dll.CardOn() 62 | if ret == 1: 63 | return {'ret': 1, 'msg': '有身份证', 'data': ''} 64 | else: 65 | return {'ret': 0, 'msg': '无身份证', 'data': ''} 66 | 67 | # 4.2.2.读卡信息接口 68 | # 原型4 69 | def read_base_infos(self): 70 | name, gender, folk, birthDay, code, address, agency, expireStart, expireEnd = bytes(192), bytes(192), bytes( 71 | 192), bytes( 72 | 192), bytes(192), bytes(192), bytes(192), bytes(192), bytes(192), 73 | ret = self.dll.ReadBaseInfos(name, gender, folk, birthDay, code, address, agency, expireStart, expireEnd) 74 | if ret == 1: 75 | info = {} 76 | info['name'] = name.decode('gbk').strip('\x00') 77 | info['gender'] = gender.decode('gbk').strip('\x00') 78 | info['folk'] = folk.decode('gbk').strip('\x00') 79 | info['birthDay'] = birthDay.decode('gbk').strip('\x00') 80 | info['code'] = code.decode('gbk').strip('\x00') 81 | info['address'] = address.decode('gbk').strip('\x00') 82 | info['agency'] = agency.decode('gbk').strip('\x00') 83 | info['expireStart'] = expireStart.decode('gbk').strip('\x00') 84 | info['expireEnd'] = expireEnd.decode('gbk').strip('\x00') 85 | 86 | try: 87 | with open("bin/photo.bmp", 'rb') as f: 88 | base64_data = base64.b64encode(f.read()) 89 | s = base64_data.decode() 90 | except Exception as e: 91 | s = '' 92 | info['photo'] = "data:image/bmp;base64," + s 93 | return {'ret': 1, 'msg': '读卡成功', 'data': info} 94 | elif ret == 0: 95 | return {'ret': 0, 'msg': '读卡失败', 'data': ''} 96 | elif ret == -4: 97 | return {'ret': 0, 'msg': '缺少文件', 'data': ''} 98 | else: 99 | return {'ret': 0, 'msg': '未知错误', 'data': ''} 100 | 101 | class OpenCV: 102 | cap = cv2.VideoCapture(0) 103 | # 从摄像头中取得视频 104 | def video_capture(self): 105 | if self.cap.isOpened(): 106 | # 读取帧摄像头 107 | ret, frame = self.cap.read() 108 | img_str = cv2.imencode('.jpg', frame)[1].tostring() # 将图片编码成流数据,放到内存缓存中,然后转化成string格式 109 | b64_code = base64.b64encode(img_str) # 编码成base64 110 | return {'ret': 1, 'msg': '视频捕获成功', 'data': 'data:image/jpg;base64,' + b64_code.decode()} 111 | else: 112 | return {'ret': 0, 'msg': '未发现摄像头', 'data': ''} 113 | 114 | # 回收资源 115 | def destroy(self): 116 | self.cap.release() 117 | -------------------------------------------------------------------------------- /snapshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zjw939057120/idcardReader-camera/bcb3e5a8ad35c0a2365b80d865712a65aff851d7/snapshot.png -------------------------------------------------------------------------------- /websocket_server/__init__.py: -------------------------------------------------------------------------------- 1 | from .websocket_server import * 2 | -------------------------------------------------------------------------------- /websocket_server/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zjw939057120/idcardReader-camera/bcb3e5a8ad35c0a2365b80d865712a65aff851d7/websocket_server/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /websocket_server/__pycache__/websocket_server.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zjw939057120/idcardReader-camera/bcb3e5a8ad35c0a2365b80d865712a65aff851d7/websocket_server/__pycache__/websocket_server.cpython-37.pyc -------------------------------------------------------------------------------- /websocket_server/websocket_server.py: -------------------------------------------------------------------------------- 1 | # Author: Johan Hanssen Seferidis 2 | # License: MIT 3 | 4 | import sys 5 | import struct 6 | from base64 import b64encode 7 | from hashlib import sha1 8 | import logging 9 | from socket import error as SocketError 10 | import errno 11 | 12 | if sys.version_info[0] < 3: 13 | from SocketServer import ThreadingMixIn, TCPServer, StreamRequestHandler 14 | else: 15 | from socketserver import ThreadingMixIn, TCPServer, StreamRequestHandler 16 | 17 | logger = logging.getLogger(__name__) 18 | logging.basicConfig() 19 | 20 | ''' 21 | +-+-+-+-+-------+-+-------------+-------------------------------+ 22 | 0 1 2 3 23 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 24 | +-+-+-+-+-------+-+-------------+-------------------------------+ 25 | |F|R|R|R| opcode|M| Payload len | Extended payload length | 26 | |I|S|S|S| (4) |A| (7) | (16/64) | 27 | |N|V|V|V| |S| | (if payload len==126/127) | 28 | | |1|2|3| |K| | | 29 | +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + 30 | | Extended payload length continued, if payload len == 127 | 31 | + - - - - - - - - - - - - - - - +-------------------------------+ 32 | | Payload Data continued ... | 33 | +---------------------------------------------------------------+ 34 | ''' 35 | 36 | FIN = 0x80 37 | OPCODE = 0x0f 38 | MASKED = 0x80 39 | PAYLOAD_LEN = 0x7f 40 | PAYLOAD_LEN_EXT16 = 0x7e 41 | PAYLOAD_LEN_EXT64 = 0x7f 42 | 43 | OPCODE_CONTINUATION = 0x0 44 | OPCODE_TEXT = 0x1 45 | OPCODE_BINARY = 0x2 46 | OPCODE_CLOSE_CONN = 0x8 47 | OPCODE_PING = 0x9 48 | OPCODE_PONG = 0xA 49 | 50 | 51 | # -------------------------------- API --------------------------------- 52 | 53 | class API(): 54 | 55 | def run_forever(self): 56 | try: 57 | logger.info("Listening on port %d for clients.." % self.port) 58 | self.serve_forever() 59 | except KeyboardInterrupt: 60 | self.server_close() 61 | logger.info("Server terminated.") 62 | except Exception as e: 63 | logger.error(str(e), exc_info=True) 64 | exit(1) 65 | 66 | def new_client(self, client, server): 67 | pass 68 | 69 | def client_left(self, client, server): 70 | pass 71 | 72 | def message_received(self, client, server, message): 73 | pass 74 | 75 | def set_fn_new_client(self, fn): 76 | self.new_client = fn 77 | 78 | def set_fn_client_left(self, fn): 79 | self.client_left = fn 80 | 81 | def set_fn_message_received(self, fn): 82 | self.message_received = fn 83 | 84 | def send_message(self, client, msg): 85 | self._unicast_(client, msg) 86 | 87 | def send_message_to_all(self, msg): 88 | self._multicast_(msg) 89 | 90 | 91 | # ------------------------- Implementation ----------------------------- 92 | 93 | class WebsocketServer(ThreadingMixIn, TCPServer, API): 94 | """ 95 | A websocket server waiting for clients to connect. 96 | 97 | Args: 98 | port(int): Port to bind to 99 | host(str): Hostname or IP to listen for connections. By default 127.0.0.1 100 | is being used. To accept connections from any client, you should use 101 | 0.0.0.0. 102 | loglevel: Logging level from logging module to use for logging. By default 103 | warnings and errors are being logged. 104 | 105 | Properties: 106 | clients(list): A list of connected clients. A client is a dictionary 107 | like below. 108 | { 109 | 'id' : id, 110 | 'handler' : handler, 111 | 'address' : (addr, port) 112 | } 113 | """ 114 | 115 | allow_reuse_address = True 116 | daemon_threads = True # comment to keep threads alive until finished 117 | 118 | clients = [] 119 | id_counter = 0 120 | 121 | def __init__(self, port, host='127.0.0.1', loglevel=logging.WARNING): 122 | logger.setLevel(loglevel) 123 | TCPServer.__init__(self, (host, port), WebSocketHandler) 124 | self.port = self.socket.getsockname()[1] 125 | 126 | def _message_received_(self, handler, msg): 127 | self.message_received(self.handler_to_client(handler), self, msg) 128 | 129 | def _ping_received_(self, handler, msg): 130 | handler.send_pong(msg) 131 | 132 | def _pong_received_(self, handler, msg): 133 | pass 134 | 135 | def _new_client_(self, handler): 136 | self.id_counter += 1 137 | client = { 138 | 'id': self.id_counter, 139 | 'handler': handler, 140 | 'address': handler.client_address 141 | } 142 | self.clients.append(client) 143 | self.new_client(client, self) 144 | 145 | def _client_left_(self, handler): 146 | client = self.handler_to_client(handler) 147 | self.client_left(client, self) 148 | if client in self.clients: 149 | self.clients.remove(client) 150 | 151 | def _unicast_(self, to_client, msg): 152 | to_client['handler'].send_message(msg) 153 | 154 | def _multicast_(self, msg): 155 | for client in self.clients: 156 | self._unicast_(client, msg) 157 | 158 | def handler_to_client(self, handler): 159 | for client in self.clients: 160 | if client['handler'] == handler: 161 | return client 162 | 163 | 164 | class WebSocketHandler(StreamRequestHandler): 165 | 166 | def __init__(self, socket, addr, server): 167 | self.server = server 168 | StreamRequestHandler.__init__(self, socket, addr, server) 169 | 170 | def setup(self): 171 | StreamRequestHandler.setup(self) 172 | self.keep_alive = True 173 | self.handshake_done = False 174 | self.valid_client = False 175 | 176 | def handle(self): 177 | while self.keep_alive: 178 | if not self.handshake_done: 179 | self.handshake() 180 | elif self.valid_client: 181 | self.read_next_message() 182 | 183 | def read_bytes(self, num): 184 | # python3 gives ordinal of byte directly 185 | bytes = self.rfile.read(num) 186 | if sys.version_info[0] < 3: 187 | return map(ord, bytes) 188 | else: 189 | return bytes 190 | 191 | def read_next_message(self): 192 | try: 193 | b1, b2 = self.read_bytes(2) 194 | except SocketError as e: # to be replaced with ConnectionResetError for py3 195 | if e.errno == errno.ECONNRESET: 196 | logger.info("Client closed connection.") 197 | print("Error: {}".format(e)) 198 | self.keep_alive = 0 199 | return 200 | b1, b2 = 0, 0 201 | except ValueError as e: 202 | b1, b2 = 0, 0 203 | 204 | fin = b1 & FIN 205 | opcode = b1 & OPCODE 206 | masked = b2 & MASKED 207 | payload_length = b2 & PAYLOAD_LEN 208 | 209 | if opcode == OPCODE_CLOSE_CONN: 210 | logger.info("Client asked to close connection.") 211 | self.keep_alive = 0 212 | return 213 | if not masked: 214 | logger.warn("Client must always be masked.") 215 | self.keep_alive = 0 216 | return 217 | if opcode == OPCODE_CONTINUATION: 218 | logger.warn("Continuation frames are not supported.") 219 | return 220 | elif opcode == OPCODE_BINARY: 221 | logger.warn("Binary frames are not supported.") 222 | return 223 | elif opcode == OPCODE_TEXT: 224 | opcode_handler = self.server._message_received_ 225 | elif opcode == OPCODE_PING: 226 | opcode_handler = self.server._ping_received_ 227 | elif opcode == OPCODE_PONG: 228 | opcode_handler = self.server._pong_received_ 229 | else: 230 | logger.warn("Unknown opcode %#x." % opcode) 231 | self.keep_alive = 0 232 | return 233 | 234 | if payload_length == 126: 235 | payload_length = struct.unpack(">H", self.rfile.read(2))[0] 236 | elif payload_length == 127: 237 | payload_length = struct.unpack(">Q", self.rfile.read(8))[0] 238 | 239 | masks = self.read_bytes(4) 240 | message_bytes = bytearray() 241 | for message_byte in self.read_bytes(payload_length): 242 | message_byte ^= masks[len(message_bytes) % 4] 243 | message_bytes.append(message_byte) 244 | opcode_handler(self, message_bytes.decode('utf8')) 245 | 246 | def send_message(self, message): 247 | self.send_text(message) 248 | 249 | def send_pong(self, message): 250 | self.send_text(message, OPCODE_PONG) 251 | 252 | def send_text(self, message, opcode=OPCODE_TEXT): 253 | """ 254 | Important: Fragmented(=continuation) messages are not supported since 255 | their usage cases are limited - when we don't know the payload length. 256 | """ 257 | 258 | # Validate message 259 | if isinstance(message, bytes): 260 | message = try_decode_UTF8(message) # this is slower but ensures we have UTF-8 261 | if not message: 262 | logger.warning("Can\'t send message, message is not valid UTF-8") 263 | return False 264 | elif sys.version_info < (3,0) and (isinstance(message, str) or isinstance(message, unicode)): 265 | pass 266 | elif isinstance(message, str): 267 | pass 268 | else: 269 | logger.warning('Can\'t send message, message has to be a string or bytes. Given type is %s' % type(message)) 270 | return False 271 | 272 | header = bytearray() 273 | payload = encode_to_UTF8(message) 274 | payload_length = len(payload) 275 | 276 | # Normal payload 277 | if payload_length <= 125: 278 | header.append(FIN | opcode) 279 | header.append(payload_length) 280 | 281 | # Extended payload 282 | elif payload_length >= 126 and payload_length <= 65535: 283 | header.append(FIN | opcode) 284 | header.append(PAYLOAD_LEN_EXT16) 285 | header.extend(struct.pack(">H", payload_length)) 286 | 287 | # Huge extended payload 288 | elif payload_length < 18446744073709551616: 289 | header.append(FIN | opcode) 290 | header.append(PAYLOAD_LEN_EXT64) 291 | header.extend(struct.pack(">Q", payload_length)) 292 | 293 | else: 294 | raise Exception("Message is too big. Consider breaking it into chunks.") 295 | return 296 | 297 | self.request.send(header + payload) 298 | 299 | def read_http_headers(self): 300 | headers = {} 301 | # first line should be HTTP GET 302 | http_get = self.rfile.readline().decode().strip() 303 | assert http_get.upper().startswith('GET') 304 | # remaining should be headers 305 | while True: 306 | header = self.rfile.readline().decode().strip() 307 | if not header: 308 | break 309 | head, value = header.split(':', 1) 310 | headers[head.lower().strip()] = value.strip() 311 | return headers 312 | 313 | def handshake(self): 314 | headers = self.read_http_headers() 315 | 316 | try: 317 | assert headers['upgrade'].lower() == 'websocket' 318 | except AssertionError: 319 | self.keep_alive = False 320 | return 321 | 322 | try: 323 | key = headers['sec-websocket-key'] 324 | except KeyError: 325 | logger.warning("Client tried to connect but was missing a key") 326 | self.keep_alive = False 327 | return 328 | 329 | response = self.make_handshake_response(key) 330 | self.handshake_done = self.request.send(response.encode()) 331 | self.valid_client = True 332 | self.server._new_client_(self) 333 | 334 | @classmethod 335 | def make_handshake_response(cls, key): 336 | return \ 337 | 'HTTP/1.1 101 Switching Protocols\r\n'\ 338 | 'Upgrade: websocket\r\n' \ 339 | 'Connection: Upgrade\r\n' \ 340 | 'Sec-WebSocket-Accept: %s\r\n' \ 341 | '\r\n' % cls.calculate_response_key(key) 342 | 343 | @classmethod 344 | def calculate_response_key(cls, key): 345 | GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' 346 | hash = sha1(key.encode() + GUID.encode()) 347 | response_key = b64encode(hash.digest()).strip() 348 | return response_key.decode('ASCII') 349 | 350 | def finish(self): 351 | self.server._client_left_(self) 352 | 353 | 354 | def encode_to_UTF8(data): 355 | try: 356 | return data.encode('UTF-8') 357 | except UnicodeEncodeError as e: 358 | logger.error("Could not encode data to UTF-8 -- %s" % e) 359 | return False 360 | except Exception as e: 361 | raise(e) 362 | return False 363 | 364 | 365 | def try_decode_UTF8(data): 366 | try: 367 | return data.decode('utf-8') 368 | except UnicodeDecodeError: 369 | return False 370 | except Exception as e: 371 | raise(e) 372 | --------------------------------------------------------------------------------