1 module sockjsclient.client; 2 3 import std.stdio; 4 import vibe.d; 5 import std.uuid; 6 import std.random; 7 import std.regex; 8 import std.string; 9 10 class SockJsClient 11 { 12 private: 13 string m_url_send; 14 string m_url_poll; 15 enum ConnectionState { none, connecting, connected, clientDisconnected, hostDisconnected } 16 ConnectionState m_connState = ConnectionState.none; 17 public: 18 19 alias void delegate() EventOnConnect; 20 alias void delegate(string) EventOnData; 21 alias void delegate(int,string) EventOnDisconnect; 22 23 EventOnConnect OnConnect; 24 EventOnData OnData; 25 EventOnDisconnect OnDisconnect; 26 27 @property bool isConnected() const { return m_connState == ConnectionState.connected; } 28 @property bool isConnecting() const { return m_connState == ConnectionState.connecting; } 29 30 /// 31 this(string host, string prefix) 32 { 33 auto randomUUId = randomUUID(); 34 auto randomInt = uniform(100,999); 35 auto url = format("%s/%s/%s/%s/xhr",host,prefix,randomInt,randomUUId); 36 m_url_poll = url; 37 m_url_send = format("%s_send",m_url_poll); 38 } 39 40 /// 41 public void connect() 42 { 43 if (m_connState == ConnectionState.none) 44 { 45 m_connState = ConnectionState.connecting; 46 StartPoll(); 47 } 48 else 49 { 50 throw new Exception("only one connect allowed"); 51 } 52 } 53 54 /// 55 public void disconnect() 56 { 57 //TODO: Should send an message on close url 58 if (m_connState == ConnectionState.connected) 59 { 60 m_connState = ConnectionState.clientDisconnected; 61 } 62 else 63 { 64 throw new Exception("Not connected"); 65 } 66 } 67 68 /// 69 public void send(string message) 70 { 71 if (m_connState == ConnectionState.connected) 72 { 73 runTask({ 74 requestHTTP(m_url_send, 75 (scope HTTPClientRequest req) { 76 req.method = HTTPMethod.POST; 77 auto prep_message = "[\""~message~"\"]"; 78 req.writeBody(cast (ubyte[])prep_message,"text/plain"); 79 }, 80 (scope res) { 81 onSendResult(res); 82 } 83 ); 84 }); 85 } 86 else 87 { 88 throw new Exception("Not connected"); 89 } 90 } 91 92 /// 93 private void onSendResult( HTTPClientResponse res) { 94 // TODO: Error handling - HTTP ERROR CODES 95 //logInfo("Response: %s", res.bodyReader.readAllUTF8()); 96 if (res.statusCode != 204) 97 { 98 throw new Exception("Send error - Status Code: " ~ to!string(res.statusCode)); 99 } 100 101 } 102 103 /// 104 private void StartPoll() 105 { 106 if (m_connState == ConnectionState.connected || m_connState == ConnectionState.connecting) 107 { 108 requestHTTP(m_url_poll, 109 (scope req) {}, 110 (scope res) { 111 onPollResult(res); 112 } 113 ); 114 } 115 else 116 { 117 throw new Exception("Not connected"); 118 } 119 } 120 121 /// 122 private void onPollResult( HTTPClientResponse res) 123 { 124 auto content = res.bodyReader.readAllUTF8(); 125 126 if (m_connState == ConnectionState.clientDisconnected) 127 return; 128 129 if (content == "o\n") 130 { 131 m_connState = ConnectionState.connected; 132 133 CallOnConnect(); 134 } 135 else if (content == "h\n") 136 { 137 //logInfo("got heartbeat"); 138 } 139 else 140 { 141 if (m_connState == ConnectionState.connected) 142 { 143 if (content[0] == 'a') 144 { 145 if (OnData != null) 146 { 147 auto arr = content[3..$-3]; 148 foreach(a; splitter(arr,regex(q"{","}"))) 149 { 150 CallOnData(a); 151 } 152 } 153 } 154 else if (content[0] == 'c') 155 { 156 m_connState = ConnectionState.hostDisconnected; 157 if (OnDisconnect != null) 158 { 159 auto closeString = content[2..$-2]; 160 auto closeArray = closeString.split(","); 161 if (closeArray.length >= 2) 162 { 163 int closeCode = closeArray[0].to!int; 164 string closeMessage = closeArray[1][1..$-1]; 165 166 CallOnDisconnect(closeCode, closeMessage); 167 } 168 else 169 { 170 CallOnDisconnect(); 171 } 172 } 173 } 174 } 175 } 176 177 if (m_connState == ConnectionState.connected || m_connState == ConnectionState.connecting) 178 { 179 runTask({ 180 StartPoll(); 181 }); 182 } 183 } 184 185 /// 186 private void CallOnConnect() 187 { 188 if(OnConnect) 189 { 190 runTask({ 191 OnConnect(); 192 }); 193 } 194 } 195 196 /// 197 private void CallOnData(string _msg) 198 { 199 if(OnData) 200 { 201 runTask({ 202 OnData(_msg); 203 }); 204 } 205 } 206 207 /// 208 private void CallOnDisconnect(int _code=0, string _msg="") 209 { 210 if(OnDisconnect) 211 { 212 runTask({ 213 if(OnDisconnect) 214 OnDisconnect(_code, _msg); 215 }); 216 } 217 } 218 } 219