├── .gitignore ├── HTML5_Websocket ├── Main.gd ├── Main.tscn ├── default_env.tres ├── icon.png └── project.godot ├── LICENSE.txt ├── LICENSE_SimpleWebSocketServer.txt ├── README.md ├── SimpleWebSocketServer ├── SimpleWebSocketServer.py └── __init__.py └── WebSocketServer.py /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | .import/ 3 | *.import 4 | export_presets.cfg 5 | exclude/ 6 | *.pyc 7 | -------------------------------------------------------------------------------- /HTML5_Websocket/Main.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | var tcp = StreamPeerTCP.new() 4 | var peer = PacketPeerStream.new() 5 | var _conn = false 6 | 7 | func _ready(): 8 | _log("Started!") 9 | tcp.connect_to_host("127.0.0.1", 8000) 10 | _log("Connecting...") 11 | set_process(true) 12 | 13 | func _process(delta): 14 | if tcp.get_status() == StreamPeerTCP.STATUS_CONNECTING: 15 | # still connecting, nothing to do 16 | pass 17 | elif tcp.get_status() == StreamPeerTCP.STATUS_CONNECTED: 18 | if _conn == false: 19 | _log("Connected!") 20 | peer.set_stream_peer(tcp) 21 | _conn = true 22 | peer.put_packet(var2bytes("First UTF-8 message: Það fer nú að verða verra ferðaveðrið")) 23 | if peer.get_available_packet_count() > 0: 24 | var pkt = peer.get_packet() 25 | _output("Got packet: " + str(Array(pkt))) 26 | _output("Packet text: " + str(bytes2var(pkt))) 27 | return 28 | elif _conn: 29 | _conn = false 30 | _log("Disconnected from server with status: " + str(tcp.get_status())) 31 | else: 32 | _log("Unable to connect, status: " + str(tcp.get_status())) 33 | set_process(false) 34 | 35 | func _output(msg): 36 | get_node("Control/Panel/VLayout/ReplyText").add_text(str(msg) + "\n") 37 | 38 | func _log(msg): 39 | get_node("Control/Panel/VLayout/LogText").add_text(str(msg) + "\n") 40 | 41 | func _on_Button_pressed(): 42 | if not _conn: 43 | _log("Cannot send, not connected") 44 | else: 45 | _log("Sending: " + get_node("Control/Panel/VLayout/HLayout/LineEdit").text) 46 | peer.put_packet(var2bytes(get_node("Control/Panel/VLayout/HLayout/LineEdit").text)) 47 | get_node("Control/Panel/VLayout/HLayout/LineEdit").clear() 48 | -------------------------------------------------------------------------------- /HTML5_Websocket/Main.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://Main.gd" type="Script" id=1] 4 | 5 | [sub_resource type="StyleBoxFlat" id=1] 6 | 7 | content_margin_left = -1.0 8 | content_margin_right = -1.0 9 | content_margin_top = -1.0 10 | content_margin_bottom = -1.0 11 | bg_color = Color( 0, 0, 0, 1 ) 12 | draw_center = true 13 | border_width_left = 0 14 | border_width_top = 0 15 | border_width_right = 0 16 | border_width_bottom = 0 17 | border_color = Color( 0.8, 0.8, 0.8, 1 ) 18 | border_blend = false 19 | corner_radius_top_left = 0 20 | corner_radius_top_right = 0 21 | corner_radius_bottom_right = 0 22 | corner_radius_bottom_left = 0 23 | corner_detail = 8 24 | expand_margin_left = 0.0 25 | expand_margin_right = 0.0 26 | expand_margin_top = 0.0 27 | expand_margin_bottom = 0.0 28 | shadow_color = Color( 0, 0, 0, 0.6 ) 29 | shadow_size = 0 30 | anti_aliasing = true 31 | anti_aliasing_size = 1 32 | 33 | [sub_resource type="StyleBoxFlat" id=2] 34 | 35 | content_margin_left = -1.0 36 | content_margin_right = -1.0 37 | content_margin_top = -1.0 38 | content_margin_bottom = -1.0 39 | bg_color = Color( 1, 0, 0, 1 ) 40 | draw_center = true 41 | border_width_left = 0 42 | border_width_top = 0 43 | border_width_right = 0 44 | border_width_bottom = 0 45 | border_color = Color( 0.8, 0.8, 0.8, 1 ) 46 | border_blend = false 47 | corner_radius_top_left = 0 48 | corner_radius_top_right = 0 49 | corner_radius_bottom_right = 0 50 | corner_radius_bottom_left = 0 51 | corner_detail = 8 52 | expand_margin_left = 0.0 53 | expand_margin_right = 0.0 54 | expand_margin_top = 0.0 55 | expand_margin_bottom = 0.0 56 | shadow_color = Color( 0, 0, 0, 0.6 ) 57 | shadow_size = 0 58 | anti_aliasing = true 59 | anti_aliasing_size = 1 60 | 61 | [node name="Node" type="Node"] 62 | 63 | script = ExtResource( 1 ) 64 | 65 | [node name="Control" type="Control" parent="."] 66 | 67 | anchor_left = 0.0 68 | anchor_top = 0.0 69 | anchor_right = 1.0 70 | anchor_bottom = 1.0 71 | rect_pivot_offset = Vector2( 0, 0 ) 72 | rect_clip_content = false 73 | mouse_filter = 0 74 | size_flags_horizontal = 1 75 | size_flags_vertical = 1 76 | _sections_unfolded = [ "Anchor", "Grow Direction", "Margin", "Rect" ] 77 | 78 | [node name="Panel" type="Panel" parent="Control"] 79 | 80 | anchor_left = 0.0 81 | anchor_top = 0.0 82 | anchor_right = 1.0 83 | anchor_bottom = 1.0 84 | rect_pivot_offset = Vector2( 0, 0 ) 85 | rect_clip_content = false 86 | mouse_filter = 0 87 | size_flags_horizontal = 1 88 | size_flags_vertical = 1 89 | 90 | [node name="VLayout" type="VBoxContainer" parent="Control/Panel"] 91 | 92 | anchor_left = 0.0 93 | anchor_top = 0.0 94 | anchor_right = 1.0 95 | anchor_bottom = 1.0 96 | rect_pivot_offset = Vector2( 0, 0 ) 97 | rect_clip_content = false 98 | mouse_filter = 1 99 | size_flags_horizontal = 1 100 | size_flags_vertical = 1 101 | alignment = 0 102 | 103 | [node name="HLayout" type="HBoxContainer" parent="Control/Panel/VLayout"] 104 | 105 | anchor_left = 0.0 106 | anchor_top = 0.0 107 | anchor_right = 0.0 108 | anchor_bottom = 0.0 109 | margin_right = 1024.0 110 | margin_bottom = 24.0 111 | rect_pivot_offset = Vector2( 0, 0 ) 112 | rect_clip_content = false 113 | mouse_filter = 1 114 | size_flags_horizontal = 1 115 | size_flags_vertical = 1 116 | alignment = 0 117 | 118 | [node name="Label" type="Label" parent="Control/Panel/VLayout/HLayout"] 119 | 120 | anchor_left = 0.0 121 | anchor_top = 0.0 122 | anchor_right = 0.0 123 | anchor_bottom = 0.0 124 | margin_top = 5.0 125 | margin_right = 99.0 126 | margin_bottom = 19.0 127 | rect_pivot_offset = Vector2( 0, 0 ) 128 | rect_clip_content = false 129 | mouse_filter = 2 130 | size_flags_horizontal = 1 131 | size_flags_vertical = 4 132 | text = "Text Goes Here" 133 | percent_visible = 1.0 134 | lines_skipped = 0 135 | max_lines_visible = -1 136 | 137 | [node name="LineEdit" type="LineEdit" parent="Control/Panel/VLayout/HLayout"] 138 | 139 | anchor_left = 0.0 140 | anchor_top = 0.0 141 | anchor_right = 0.0 142 | anchor_bottom = 0.0 143 | margin_left = 103.0 144 | margin_right = 977.0 145 | margin_bottom = 24.0 146 | rect_pivot_offset = Vector2( 0, 0 ) 147 | rect_clip_content = false 148 | mouse_filter = 0 149 | size_flags_horizontal = 3 150 | size_flags_vertical = 1 151 | expand_to_len = false 152 | focus_mode = 2 153 | placeholder_alpha = 0.6 154 | caret_blink = false 155 | caret_blink_speed = 0.65 156 | _sections_unfolded = [ "Size Flags" ] 157 | 158 | [node name="Button" type="Button" parent="Control/Panel/VLayout/HLayout"] 159 | 160 | anchor_left = 0.0 161 | anchor_top = 0.0 162 | anchor_right = 0.0 163 | anchor_bottom = 0.0 164 | margin_left = 981.0 165 | margin_right = 1024.0 166 | margin_bottom = 24.0 167 | rect_pivot_offset = Vector2( 0, 0 ) 168 | rect_clip_content = false 169 | mouse_filter = 0 170 | size_flags_horizontal = 1 171 | size_flags_vertical = 1 172 | toggle_mode = false 173 | enabled_focus_mode = 2 174 | shortcut = null 175 | group = null 176 | text = "Send" 177 | flat = false 178 | 179 | [node name="Reply" type="Label" parent="Control/Panel/VLayout"] 180 | 181 | anchor_left = 0.0 182 | anchor_top = 0.0 183 | anchor_right = 0.0 184 | anchor_bottom = 0.0 185 | margin_top = 28.0 186 | margin_right = 1024.0 187 | margin_bottom = 42.0 188 | rect_pivot_offset = Vector2( 0, 0 ) 189 | rect_clip_content = false 190 | mouse_filter = 2 191 | size_flags_horizontal = 1 192 | size_flags_vertical = 4 193 | text = "Reply will come up here" 194 | percent_visible = 1.0 195 | lines_skipped = 0 196 | max_lines_visible = -1 197 | 198 | [node name="ReplyText" type="RichTextLabel" parent="Control/Panel/VLayout"] 199 | 200 | anchor_left = 0.0 201 | anchor_top = 0.0 202 | anchor_right = 0.0 203 | anchor_bottom = 0.0 204 | margin_top = 46.0 205 | margin_right = 1024.0 206 | margin_bottom = 312.0 207 | rect_pivot_offset = Vector2( 0, 0 ) 208 | mouse_filter = 0 209 | size_flags_horizontal = 3 210 | size_flags_vertical = 3 211 | custom_styles/normal = SubResource( 1 ) 212 | bbcode_enabled = false 213 | bbcode_text = "" 214 | visible_characters = -1 215 | percent_visible = 1.0 216 | _sections_unfolded = [ "Size Flags", "custom_styles" ] 217 | 218 | [node name="Log" type="Label" parent="Control/Panel/VLayout"] 219 | 220 | anchor_left = 0.0 221 | anchor_top = 0.0 222 | anchor_right = 0.0 223 | anchor_bottom = 0.0 224 | margin_top = 316.0 225 | margin_right = 1024.0 226 | margin_bottom = 330.0 227 | rect_pivot_offset = Vector2( 0, 0 ) 228 | rect_clip_content = false 229 | mouse_filter = 2 230 | size_flags_horizontal = 1 231 | size_flags_vertical = 4 232 | text = "Log will be here" 233 | percent_visible = 1.0 234 | lines_skipped = 0 235 | max_lines_visible = -1 236 | 237 | [node name="LogText" type="RichTextLabel" parent="Control/Panel/VLayout"] 238 | 239 | anchor_left = 0.0 240 | anchor_top = 0.0 241 | anchor_right = 0.0 242 | anchor_bottom = 0.0 243 | margin_top = 334.0 244 | margin_right = 1024.0 245 | margin_bottom = 600.0 246 | rect_pivot_offset = Vector2( 0, 0 ) 247 | mouse_filter = 0 248 | size_flags_horizontal = 3 249 | size_flags_vertical = 3 250 | custom_styles/normal = SubResource( 2 ) 251 | bbcode_enabled = false 252 | bbcode_text = "" 253 | visible_characters = -1 254 | percent_visible = 1.0 255 | _sections_unfolded = [ "Size Flags", "custom_styles" ] 256 | 257 | [connection signal="pressed" from="Control/Panel/VLayout/HLayout/Button" to="." method="_on_Button_pressed"] 258 | 259 | 260 | -------------------------------------------------------------------------------- /HTML5_Websocket/default_env.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Environment" load_steps=2 format=2] 2 | 3 | [sub_resource type="ProceduralSky" id=1] 4 | 5 | radiance_size = 4 6 | sky_top_color = Color( 0.0470588, 0.454902, 0.976471, 1 ) 7 | sky_horizon_color = Color( 0.556863, 0.823529, 0.909804, 1 ) 8 | sky_curve = 0.25 9 | sky_energy = 1.0 10 | ground_bottom_color = Color( 0.101961, 0.145098, 0.188235, 1 ) 11 | ground_horizon_color = Color( 0.482353, 0.788235, 0.952941, 1 ) 12 | ground_curve = 0.01 13 | ground_energy = 1.0 14 | sun_color = Color( 1, 1, 1, 1 ) 15 | sun_latitude = 35.0 16 | sun_longitude = 0.0 17 | sun_angle_min = 1.0 18 | sun_angle_max = 100.0 19 | sun_curve = 0.05 20 | sun_energy = 16.0 21 | texture_size = 2 22 | 23 | [resource] 24 | 25 | background_mode = 2 26 | background_sky = SubResource( 1 ) 27 | background_sky_scale = 1.0 28 | background_color = Color( 0, 0, 0, 1 ) 29 | background_energy = 1.0 30 | background_canvas_max_layer = 0 31 | ambient_light_color = Color( 0, 0, 0, 1 ) 32 | ambient_light_energy = 1.0 33 | ambient_light_sky_contribution = 1.0 34 | fog_enabled = false 35 | fog_color = Color( 0.5, 0.6, 0.7, 1 ) 36 | fog_sun_color = Color( 1, 0.9, 0.7, 1 ) 37 | fog_sun_amount = 0.0 38 | fog_depth_enabled = true 39 | fog_depth_begin = 10.0 40 | fog_depth_curve = 1.0 41 | fog_transmit_enabled = false 42 | fog_transmit_curve = 1.0 43 | fog_height_enabled = false 44 | fog_height_min = 0.0 45 | fog_height_max = 100.0 46 | fog_height_curve = 1.0 47 | tonemap_mode = 0 48 | tonemap_exposure = 1.0 49 | tonemap_white = 1.0 50 | auto_exposure_enabled = false 51 | auto_exposure_scale = 0.4 52 | auto_exposure_min_luma = 0.05 53 | auto_exposure_max_luma = 8.0 54 | auto_exposure_speed = 0.5 55 | ss_reflections_enabled = false 56 | ss_reflections_max_steps = 64 57 | ss_reflections_fade_in = 0.15 58 | ss_reflections_fade_out = 2.0 59 | ss_reflections_depth_tolerance = 0.2 60 | ss_reflections_roughness = true 61 | ssao_enabled = false 62 | ssao_radius = 1.0 63 | ssao_intensity = 1.0 64 | ssao_radius2 = 0.0 65 | ssao_intensity2 = 1.0 66 | ssao_bias = 0.01 67 | ssao_light_affect = 0.0 68 | ssao_color = Color( 0, 0, 0, 1 ) 69 | ssao_blur = true 70 | dof_blur_far_enabled = false 71 | dof_blur_far_distance = 10.0 72 | dof_blur_far_transition = 5.0 73 | dof_blur_far_amount = 0.1 74 | dof_blur_far_quality = 1 75 | dof_blur_near_enabled = false 76 | dof_blur_near_distance = 2.0 77 | dof_blur_near_transition = 1.0 78 | dof_blur_near_amount = 0.1 79 | dof_blur_near_quality = 1 80 | glow_enabled = false 81 | glow_levels/1 = false 82 | glow_levels/2 = false 83 | glow_levels/3 = true 84 | glow_levels/4 = false 85 | glow_levels/5 = true 86 | glow_levels/6 = false 87 | glow_levels/7 = false 88 | glow_intensity = 0.8 89 | glow_strength = 1.0 90 | glow_bloom = 0.0 91 | glow_blend_mode = 2 92 | glow_hdr_threshold = 1.0 93 | glow_hdr_scale = 2.0 94 | glow_bicubic_upscale = false 95 | adjustment_enabled = false 96 | adjustment_brightness = 1.0 97 | adjustment_contrast = 1.0 98 | adjustment_saturation = 1.0 99 | 100 | -------------------------------------------------------------------------------- /HTML5_Websocket/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Faless/godot-websocket-demo/0095c858bf19495030f64954a196c9db7942e147/HTML5_Websocket/icon.png -------------------------------------------------------------------------------- /HTML5_Websocket/project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=3 10 | 11 | [application] 12 | 13 | config/name="HTML5 Websocket Example" 14 | run/main_scene="res://Main.tscn" 15 | config/icon="res://icon.png" 16 | 17 | [gdnative] 18 | 19 | singletons=[ ] 20 | 21 | [rendering] 22 | 23 | environment/default_environment="res://default_env.tres" 24 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-2017 Fabio Alessandrelli 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE_SimpleWebSocketServer.txt: -------------------------------------------------------------------------------- 1 | See https://github.com/dpallot/simple-websocket-server 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Godot 3.0 HTML5 WebSocket example 2 | 3 | # This demo was for Godot 3.0 and used emscripten automatic conversion of TCP sockets to websockets. This demo is deprecated, as there is now official WebSocket support via WebSocketClient and WebSocketServer (godot 3.1+) 4 | 5 | This is a small demo to show usage of WebSocket feature in HTML5 Godot export: 6 | 7 | * The WebSocket Server used is a fork of https://github.com/dpallot/simple-websocket-server (see https://github.com/Faless/simple-websocket-server) allowing binary sub-protocol 8 | * Remember to run the WebSocket Server before running the game (`python WebSocketServer.py` should suffice) 9 | * You need a proper web server to serve the exported game for it to work on Chrome (it won't allow XHR from `file://`) 10 | * WebSockets only works on HTML5 export, it won't work when running from the editor unless you implement the websocket protocol yourself. 11 | 12 | 13 | The Godot demo is released under MIT license. 14 | 15 | The WebSocket server is MIT licensed too apparently, but I do not own the copyright of it, see: https://github.com/dpallot/simple-websocket-server 16 | 17 | 18 | Tested on Chromium and Firefox (not in privacy mode, due to current Godot limitation) 19 | -------------------------------------------------------------------------------- /SimpleWebSocketServer/SimpleWebSocketServer.py: -------------------------------------------------------------------------------- 1 | ''' 2 | The MIT License (MIT) 3 | Copyright (c) 2013 Dave P. 4 | ''' 5 | import sys 6 | VER = sys.version_info[0] 7 | if VER >= 3: 8 | import socketserver 9 | from http.server import BaseHTTPRequestHandler 10 | from io import StringIO, BytesIO 11 | else: 12 | import SocketServer 13 | from BaseHTTPServer import BaseHTTPRequestHandler 14 | from StringIO import StringIO 15 | 16 | import hashlib 17 | import base64 18 | import socket 19 | import struct 20 | import ssl 21 | import errno 22 | import codecs 23 | from collections import deque 24 | from select import select 25 | 26 | __all__ = ['WebSocket', 27 | 'SimpleWebSocketServer', 28 | 'SimpleSSLWebSocketServer'] 29 | 30 | def _check_unicode(val): 31 | if VER >= 3: 32 | return isinstance(val, str) 33 | else: 34 | return isinstance(val, unicode) 35 | 36 | class HTTPRequest(BaseHTTPRequestHandler): 37 | def __init__(self, request_text): 38 | if VER >= 3: 39 | self.rfile = BytesIO(request_text) 40 | else: 41 | self.rfile = StringIO(request_text) 42 | self.raw_requestline = self.rfile.readline() 43 | self.error_code = self.error_message = None 44 | self.parse_request() 45 | 46 | _VALID_STATUS_CODES = [1000, 1001, 1002, 1003, 1007, 1008, 47 | 1009, 1010, 1011, 3000, 3999, 4000, 4999] 48 | 49 | HANDSHAKE_STR = ( 50 | "HTTP/1.1 101 Switching Protocols\r\n" 51 | "Upgrade: WebSocket\r\n" 52 | "Connection: Upgrade\r\n" 53 | "Sec-WebSocket-Protocol: binary\r\n" 54 | "Sec-WebSocket-Accept: %(acceptstr)s\r\n\r\n" 55 | ) 56 | 57 | GUID_STR = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' 58 | 59 | STREAM = 0x0 60 | TEXT = 0x1 61 | BINARY = 0x2 62 | CLOSE = 0x8 63 | PING = 0x9 64 | PONG = 0xA 65 | 66 | HEADERB1 = 1 67 | HEADERB2 = 3 68 | LENGTHSHORT = 4 69 | LENGTHLONG = 5 70 | MASK = 6 71 | PAYLOAD = 7 72 | 73 | MAXHEADER = 65536 74 | MAXPAYLOAD = 33554432 75 | 76 | class WebSocket(object): 77 | 78 | def __init__(self, server, sock, address): 79 | self.server = server 80 | self.client = sock 81 | self.address = address 82 | 83 | self.handshaked = False 84 | self.headerbuffer = bytearray() 85 | self.headertoread = 2048 86 | 87 | self.fin = 0 88 | self.data = bytearray() 89 | self.opcode = 0 90 | self.hasmask = 0 91 | self.maskarray = None 92 | self.length = 0 93 | self.lengtharray = None 94 | self.index = 0 95 | self.request = None 96 | self.usingssl = False 97 | 98 | self.frag_start = False 99 | self.frag_type = BINARY 100 | self.frag_buffer = None 101 | self.frag_decoder = codecs.getincrementaldecoder('utf-8')(errors='strict') 102 | self.closed = False 103 | self.sendq = deque() 104 | 105 | self.state = HEADERB1 106 | 107 | # restrict the size of header and payload for security reasons 108 | self.maxheader = MAXHEADER 109 | self.maxpayload = MAXPAYLOAD 110 | 111 | def handleMessage(self): 112 | """ 113 | Called when websocket frame is received. 114 | To access the frame data call self.data. 115 | 116 | If the frame is Text then self.data is a unicode object. 117 | If the frame is Binary then self.data is a bytearray object. 118 | """ 119 | pass 120 | 121 | def handleConnected(self): 122 | """ 123 | Called when a websocket client connects to the server. 124 | """ 125 | pass 126 | 127 | def handleClose(self): 128 | """ 129 | Called when a websocket server gets a Close frame from a client. 130 | """ 131 | pass 132 | 133 | def _handlePacket(self): 134 | if self.opcode == CLOSE: 135 | pass 136 | elif self.opcode == STREAM: 137 | pass 138 | elif self.opcode == TEXT: 139 | pass 140 | elif self.opcode == BINARY: 141 | pass 142 | elif self.opcode == PONG or self.opcode == PING: 143 | if len(self.data) > 125: 144 | raise Exception('control frame length can not be > 125') 145 | else: 146 | # unknown or reserved opcode so just close 147 | raise Exception('unknown opcode') 148 | 149 | if self.opcode == CLOSE: 150 | status = 1000 151 | reason = u'' 152 | length = len(self.data) 153 | 154 | if length == 0: 155 | pass 156 | elif length >= 2: 157 | status = struct.unpack_from('!H', self.data[:2])[0] 158 | reason = self.data[2:] 159 | 160 | if status not in _VALID_STATUS_CODES: 161 | status = 1002 162 | 163 | if len(reason) > 0: 164 | try: 165 | reason = reason.decode('utf8', errors='strict') 166 | except: 167 | status = 1002 168 | else: 169 | status = 1002 170 | 171 | self.close(status, reason) 172 | return 173 | 174 | elif self.fin == 0: 175 | if self.opcode != STREAM: 176 | if self.opcode == PING or self.opcode == PONG: 177 | raise Exception('control messages can not be fragmented') 178 | 179 | self.frag_type = self.opcode 180 | self.frag_start = True 181 | self.frag_decoder.reset() 182 | 183 | if self.frag_type == TEXT: 184 | self.frag_buffer = [] 185 | utf_str = self.frag_decoder.decode(self.data, final = False) 186 | if utf_str: 187 | self.frag_buffer.append(utf_str) 188 | else: 189 | self.frag_buffer = bytearray() 190 | self.frag_buffer.extend(self.data) 191 | 192 | else: 193 | if self.frag_start is False: 194 | raise Exception('fragmentation protocol error') 195 | 196 | if self.frag_type == TEXT: 197 | utf_str = self.frag_decoder.decode(self.data, final = False) 198 | if utf_str: 199 | self.frag_buffer.append(utf_str) 200 | else: 201 | self.frag_buffer.extend(self.data) 202 | 203 | else: 204 | if self.opcode == STREAM: 205 | if self.frag_start is False: 206 | raise Exception('fragmentation protocol error') 207 | 208 | if self.frag_type == TEXT: 209 | utf_str = self.frag_decoder.decode(self.data, final = True) 210 | self.frag_buffer.append(utf_str) 211 | self.data = u''.join(self.frag_buffer) 212 | else: 213 | self.frag_buffer.extend(self.data) 214 | self.data = self.frag_buffer 215 | 216 | self.handleMessage() 217 | 218 | self.frag_decoder.reset() 219 | self.frag_type = BINARY 220 | self.frag_start = False 221 | self.frag_buffer = None 222 | 223 | elif self.opcode == PING: 224 | self._sendMessage(False, PONG, self.data) 225 | 226 | elif self.opcode == PONG: 227 | pass 228 | 229 | else: 230 | if self.frag_start is True: 231 | raise Exception('fragmentation protocol error') 232 | 233 | if self.opcode == TEXT: 234 | try: 235 | self.data = self.data.decode('utf8', errors='strict') 236 | except Exception as exp: 237 | raise Exception('invalid utf-8 payload') 238 | 239 | self.handleMessage() 240 | 241 | 242 | def _handleData(self): 243 | # do the HTTP header and handshake 244 | if self.handshaked is False: 245 | 246 | data = self.client.recv(self.headertoread) 247 | if not data: 248 | raise Exception('remote socket closed') 249 | 250 | else: 251 | # accumulate 252 | self.headerbuffer.extend(data) 253 | 254 | if len(self.headerbuffer) >= self.maxheader: 255 | raise Exception('header exceeded allowable size') 256 | 257 | # indicates end of HTTP header 258 | if b'\r\n\r\n' in self.headerbuffer: 259 | self.request = HTTPRequest(self.headerbuffer) 260 | 261 | # handshake rfc 6455 262 | try: 263 | key = self.request.headers['Sec-WebSocket-Key'] 264 | k = key.encode('ascii') + GUID_STR.encode('ascii') 265 | k_s = base64.b64encode(hashlib.sha1(k).digest()).decode('ascii') 266 | hStr = HANDSHAKE_STR % {'acceptstr': k_s} 267 | self.sendq.append((BINARY, hStr.encode('ascii'))) 268 | self.handshaked = True 269 | self.handleConnected() 270 | except Exception as e: 271 | raise Exception('handshake failed: %s', str(e)) 272 | 273 | # else do normal data 274 | else: 275 | data = self.client.recv(16384) 276 | if not data: 277 | raise Exception("remote socket closed") 278 | 279 | if VER >= 3: 280 | for d in data: 281 | self._parseMessage(d) 282 | else: 283 | for d in data: 284 | self._parseMessage(ord(d)) 285 | 286 | def close(self, status = 1000, reason = u''): 287 | """ 288 | Send Close frame to the client. The underlying socket is only closed 289 | when the client acknowledges the Close frame. 290 | 291 | status is the closing identifier. 292 | reason is the reason for the close. 293 | """ 294 | try: 295 | if self.closed is False: 296 | close_msg = bytearray() 297 | close_msg.extend(struct.pack("!H", status)) 298 | if _check_unicode(reason): 299 | close_msg.extend(reason.encode('utf-8')) 300 | else: 301 | close_msg.extend(reason) 302 | 303 | self._sendMessage(False, CLOSE, close_msg) 304 | 305 | finally: 306 | self.closed = True 307 | 308 | 309 | def _sendBuffer(self, buff, send_all = False): 310 | size = len(buff) 311 | tosend = size 312 | already_sent = 0 313 | 314 | while tosend > 0: 315 | try: 316 | # i should be able to send a bytearray 317 | sent = self.client.send(buff[already_sent:]) 318 | if sent == 0: 319 | raise RuntimeError('socket connection broken') 320 | 321 | already_sent += sent 322 | tosend -= sent 323 | 324 | except socket.error as e: 325 | # if we have full buffers then wait for them to drain and try again 326 | if e.errno in [errno.EAGAIN, errno.EWOULDBLOCK]: 327 | if send_all: 328 | continue 329 | return buff[already_sent:] 330 | else: 331 | raise e 332 | 333 | return None 334 | 335 | def sendFragmentStart(self, data): 336 | """ 337 | Send the start of a data fragment stream to a websocket client. 338 | Subsequent data should be sent using sendFragment(). 339 | A fragment stream is completed when sendFragmentEnd() is called. 340 | 341 | If data is a unicode object then the frame is sent as Text. 342 | If the data is a bytearray object then the frame is sent as Binary. 343 | """ 344 | opcode = BINARY 345 | if _check_unicode(data): 346 | opcode = TEXT 347 | self._sendMessage(True, opcode, data) 348 | 349 | def sendFragment(self, data): 350 | """ 351 | see sendFragmentStart() 352 | 353 | If data is a unicode object then the frame is sent as Text. 354 | If the data is a bytearray object then the frame is sent as Binary. 355 | """ 356 | self._sendMessage(True, STREAM, data) 357 | 358 | def sendFragmentEnd(self, data): 359 | """ 360 | see sendFragmentEnd() 361 | 362 | If data is a unicode object then the frame is sent as Text. 363 | If the data is a bytearray object then the frame is sent as Binary. 364 | """ 365 | self._sendMessage(False, STREAM, data) 366 | 367 | def sendMessage(self, data): 368 | """ 369 | Send websocket data frame to the client. 370 | 371 | If data is a unicode object then the frame is sent as Text. 372 | If the data is a bytearray object then the frame is sent as Binary. 373 | """ 374 | opcode = BINARY 375 | if _check_unicode(data): 376 | opcode = TEXT 377 | self._sendMessage(False, opcode, data) 378 | 379 | 380 | def _sendMessage(self, fin, opcode, data): 381 | 382 | payload = bytearray() 383 | 384 | b1 = 0 385 | b2 = 0 386 | if fin is False: 387 | b1 |= 0x80 388 | b1 |= opcode 389 | 390 | if _check_unicode(data): 391 | data = data.encode('utf-8') 392 | 393 | length = len(data) 394 | payload.append(b1) 395 | 396 | if length <= 125: 397 | b2 |= length 398 | payload.append(b2) 399 | 400 | elif length >= 126 and length <= 65535: 401 | b2 |= 126 402 | payload.append(b2) 403 | payload.extend(struct.pack("!H", length)) 404 | 405 | else: 406 | b2 |= 127 407 | payload.append(b2) 408 | payload.extend(struct.pack("!Q", length)) 409 | 410 | if length > 0: 411 | payload.extend(data) 412 | 413 | self.sendq.append((opcode, payload)) 414 | 415 | 416 | def _parseMessage(self, byte): 417 | # read in the header 418 | if self.state == HEADERB1: 419 | 420 | self.fin = byte & 0x80 421 | self.opcode = byte & 0x0F 422 | self.state = HEADERB2 423 | 424 | self.index = 0 425 | self.length = 0 426 | self.lengtharray = bytearray() 427 | self.data = bytearray() 428 | 429 | rsv = byte & 0x70 430 | if rsv != 0: 431 | raise Exception('RSV bit must be 0') 432 | 433 | elif self.state == HEADERB2: 434 | mask = byte & 0x80 435 | length = byte & 0x7F 436 | 437 | if self.opcode == PING and length > 125: 438 | raise Exception('ping packet is too large') 439 | 440 | if mask == 128: 441 | self.hasmask = True 442 | else: 443 | self.hasmask = False 444 | 445 | if length <= 125: 446 | self.length = length 447 | 448 | # if we have a mask we must read it 449 | if self.hasmask is True: 450 | self.maskarray = bytearray() 451 | self.state = MASK 452 | else: 453 | # if there is no mask and no payload we are done 454 | if self.length <= 0: 455 | try: 456 | self._handlePacket() 457 | finally: 458 | self.state = HEADERB1 459 | self.data = bytearray() 460 | 461 | # we have no mask and some payload 462 | else: 463 | #self.index = 0 464 | self.data = bytearray() 465 | self.state = PAYLOAD 466 | 467 | elif length == 126: 468 | self.lengtharray = bytearray() 469 | self.state = LENGTHSHORT 470 | 471 | elif length == 127: 472 | self.lengtharray = bytearray() 473 | self.state = LENGTHLONG 474 | 475 | 476 | elif self.state == LENGTHSHORT: 477 | self.lengtharray.append(byte) 478 | 479 | if len(self.lengtharray) > 2: 480 | raise Exception('short length exceeded allowable size') 481 | 482 | if len(self.lengtharray) == 2: 483 | self.length = struct.unpack_from('!H', self.lengtharray)[0] 484 | 485 | if self.hasmask is True: 486 | self.maskarray = bytearray() 487 | self.state = MASK 488 | else: 489 | # if there is no mask and no payload we are done 490 | if self.length <= 0: 491 | try: 492 | self._handlePacket() 493 | finally: 494 | self.state = HEADERB1 495 | self.data = bytearray() 496 | 497 | # we have no mask and some payload 498 | else: 499 | #self.index = 0 500 | self.data = bytearray() 501 | self.state = PAYLOAD 502 | 503 | elif self.state == LENGTHLONG: 504 | 505 | self.lengtharray.append(byte) 506 | 507 | if len(self.lengtharray) > 8: 508 | raise Exception('long length exceeded allowable size') 509 | 510 | if len(self.lengtharray) == 8: 511 | self.length = struct.unpack_from('!Q', self.lengtharray)[0] 512 | 513 | if self.hasmask is True: 514 | self.maskarray = bytearray() 515 | self.state = MASK 516 | else: 517 | # if there is no mask and no payload we are done 518 | if self.length <= 0: 519 | try: 520 | self._handlePacket() 521 | finally: 522 | self.state = HEADERB1 523 | self.data = bytearray() 524 | 525 | # we have no mask and some payload 526 | else: 527 | #self.index = 0 528 | self.data = bytearray() 529 | self.state = PAYLOAD 530 | 531 | # MASK STATE 532 | elif self.state == MASK: 533 | self.maskarray.append(byte) 534 | 535 | if len(self.maskarray) > 4: 536 | raise Exception('mask exceeded allowable size') 537 | 538 | if len(self.maskarray) == 4: 539 | # if there is no mask and no payload we are done 540 | if self.length <= 0: 541 | try: 542 | self._handlePacket() 543 | finally: 544 | self.state = HEADERB1 545 | self.data = bytearray() 546 | 547 | # we have no mask and some payload 548 | else: 549 | #self.index = 0 550 | self.data = bytearray() 551 | self.state = PAYLOAD 552 | 553 | # PAYLOAD STATE 554 | elif self.state == PAYLOAD: 555 | if self.hasmask is True: 556 | self.data.append( byte ^ self.maskarray[self.index % 4] ) 557 | else: 558 | self.data.append( byte ) 559 | 560 | # if length exceeds allowable size then we except and remove the connection 561 | if len(self.data) >= self.maxpayload: 562 | raise Exception('payload exceeded allowable size') 563 | 564 | # check if we have processed length bytes; if so we are done 565 | if (self.index+1) == self.length: 566 | try: 567 | self._handlePacket() 568 | finally: 569 | #self.index = 0 570 | self.state = HEADERB1 571 | self.data = bytearray() 572 | else: 573 | self.index += 1 574 | 575 | 576 | class SimpleWebSocketServer(object): 577 | def __init__(self, host, port, websocketclass, selectInterval = 0.1): 578 | self.websocketclass = websocketclass 579 | self.serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 580 | self.serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 581 | self.serversocket.bind((host, port)) 582 | self.serversocket.listen(5) 583 | self.selectInterval = selectInterval 584 | self.connections = {} 585 | self.listeners = [self.serversocket] 586 | 587 | def _decorateSocket(self, sock): 588 | return sock 589 | 590 | def _constructWebSocket(self, sock, address): 591 | return self.websocketclass(self, sock, address) 592 | 593 | def close(self): 594 | self.serversocket.close() 595 | 596 | for desc, conn in self.connections.items(): 597 | conn.close() 598 | self._handleClose(conn) 599 | 600 | def _handleClose(self, client): 601 | client.client.close() 602 | # only call handleClose when we have a successful websocket connection 603 | if client.handshaked: 604 | try: 605 | client.handleClose() 606 | except: 607 | pass 608 | 609 | def serveonce(self): 610 | writers = [] 611 | for fileno in self.listeners: 612 | if fileno == self.serversocket: 613 | continue 614 | client = self.connections[fileno] 615 | if client.sendq: 616 | writers.append(fileno) 617 | 618 | if self.selectInterval: 619 | rList, wList, xList = select(self.listeners, writers, self.listeners, self.selectInterval) 620 | else: 621 | rList, wList, xList = select(self.listeners, writers, self.listeners) 622 | 623 | for ready in wList: 624 | client = self.connections[ready] 625 | try: 626 | while client.sendq: 627 | opcode, payload = client.sendq.popleft() 628 | remaining = client._sendBuffer(payload) 629 | if remaining is not None: 630 | client.sendq.appendleft((opcode, remaining)) 631 | break 632 | else: 633 | if opcode == CLOSE: 634 | raise Exception('received client close') 635 | 636 | except Exception as n: 637 | self._handleClose(client) 638 | del self.connections[ready] 639 | self.listeners.remove(ready) 640 | 641 | for ready in rList: 642 | if ready == self.serversocket: 643 | try: 644 | sock, address = self.serversocket.accept() 645 | newsock = self._decorateSocket(sock) 646 | newsock.setblocking(0) 647 | fileno = newsock.fileno() 648 | self.connections[fileno] = self._constructWebSocket(newsock, address) 649 | self.listeners.append(fileno) 650 | except Exception as n: 651 | if sock is not None: 652 | sock.close() 653 | else: 654 | if ready not in self.connections: 655 | continue 656 | client = self.connections[ready] 657 | try: 658 | client._handleData() 659 | except Exception as n: 660 | self._handleClose(client) 661 | del self.connections[ready] 662 | self.listeners.remove(ready) 663 | 664 | for failed in xList: 665 | if failed == self.serversocket: 666 | self.close() 667 | raise Exception('server socket failed') 668 | else: 669 | if failed not in self.connections: 670 | continue 671 | client = self.connections[failed] 672 | self._handleClose(client) 673 | del self.connections[failed] 674 | self.listeners.remove(failed) 675 | 676 | def serveforever(self): 677 | while True: 678 | self.serveonce() 679 | 680 | class SimpleSSLWebSocketServer(SimpleWebSocketServer): 681 | 682 | def __init__(self, host, port, websocketclass, certfile, 683 | keyfile, version = ssl.PROTOCOL_TLSv1, selectInterval = 0.1): 684 | 685 | SimpleWebSocketServer.__init__(self, host, port, 686 | websocketclass, selectInterval) 687 | 688 | self.context = ssl.SSLContext(version) 689 | self.context.load_cert_chain(certfile, keyfile) 690 | 691 | def close(self): 692 | super(SimpleSSLWebSocketServer, self).close() 693 | 694 | def _decorateSocket(self, sock): 695 | sslsock = self.context.wrap_socket(sock, server_side=True) 696 | return sslsock 697 | 698 | def _constructWebSocket(self, sock, address): 699 | ws = self.websocketclass(self, sock, address) 700 | ws.usingssl = True 701 | return ws 702 | 703 | def serveforever(self): 704 | super(SimpleSSLWebSocketServer, self).serveforever() 705 | -------------------------------------------------------------------------------- /SimpleWebSocketServer/__init__.py: -------------------------------------------------------------------------------- 1 | from .SimpleWebSocketServer import * 2 | -------------------------------------------------------------------------------- /WebSocketServer.py: -------------------------------------------------------------------------------- 1 | from SimpleWebSocketServer import SimpleWebSocketServer, WebSocket 2 | 3 | class SimpleEcho(WebSocket): 4 | 5 | def handleMessage(self): 6 | # echo message back to client 7 | self.sendMessage(self.data) 8 | 9 | def handleConnected(self): 10 | print(self.address, 'connected') 11 | 12 | def handleClose(self): 13 | print(self.address, 'closed') 14 | 15 | server = SimpleWebSocketServer('', 8000, SimpleEcho) 16 | server.serveforever() 17 | --------------------------------------------------------------------------------