1 module monitor; 2 3 import core.memory; 4 5 import std.array; 6 import std.conv; 7 import std.datetime; 8 import std.regex; 9 import std.socket; 10 import std.string; 11 import std.stdio; 12 13 14 alias Now = Clock.currTime; 15 16 17 struct URL { 18 static URL parse(string url) { 19 auto matches = matchFirst(url, ctRegex!(r"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$", "i")); 20 21 URL result; 22 if (!matches.empty) { 23 result.protocol = matches[2].toLower; 24 auto host = matches[4].toLower; 25 auto index = host.indexOf(':'); 26 27 if (index != -1) { 28 result.port = host[index + 1..$].to!ushort; 29 result.host = host[0..index]; 30 } else { 31 switch(result.protocol) { 32 case "http": 33 result.port = 80; 34 break; 35 case "https": 36 result.port = 443; 37 break; 38 default: 39 break; 40 } 41 result.host = host; 42 } 43 44 result.path = matches[5]; 45 result.query = matches[7]; 46 result.anchor = matches[9]; 47 } 48 49 return result; 50 } 51 52 string protocol; 53 string host; 54 ushort port; 55 string path; 56 string query; 57 string anchor; 58 } 59 60 61 struct HTTPMonitor { 62 enum State : uint { 63 Reset, 64 Ready, 65 Idle, 66 Connect, 67 Connecting, 68 Connected, 69 Request, 70 Requesting, 71 Requested, 72 Success, 73 Failure, 74 GraceFailure, 75 } 76 77 ~this() { 78 reset(); 79 } 80 81 void reset() { 82 close(); 83 try_ = 0; 84 85 changeState(State.Reset); 86 } 87 88 void start(string url, size_t intervalMS, size_t timeoutMS, size_t graceMS, size_t retries) { 89 url_ = URL.parse(url); 90 timeoutMS_ = timeoutMS; 91 intervalMS_ = intervalMS; 92 graceMS_ = graceMS; 93 retryCount_ = retries; 94 95 close(); 96 try_ = 0; 97 98 changeState(State.Ready); 99 monitorStart_ = Now; 100 } 101 102 private void close() { 103 if (socket_ !is null) { 104 socket_.shutdown(SocketShutdown.SEND); 105 socket_.close(); 106 107 destroy(socket_); 108 socket_ = null; 109 110 GC.collect(); 111 } 112 } 113 114 private void changeState(State state) { 115 state_ = state; 116 } 117 118 void update() { 119 final switch (state_) with (State) { 120 case Reset: 121 break; 122 123 case Ready: 124 changeState(Connect); 125 goto case Connect; 126 127 case Idle: 128 if (((Now - testStart_).total!"msecs" >= intervalMS_)) { 129 changeState(Connect); 130 goto case Connect; 131 } 132 break; 133 134 case Connect: 135 assert(socket_ is null); 136 try { 137 testStart_ = Now; 138 socket_ = new TcpSocket(); 139 socket_.blocking = false; 140 141 socket_.connect(new InternetAddress(url_.host, url_.port)); 142 143 changeState(Connecting); 144 goto case Connecting; 145 } catch (Throwable e) { 146 writeln(e.toString); 147 changeState(Failure); 148 goto case Failure; 149 } 150 151 case Connecting: 152 changeState(Connected); 153 goto case Connected; 154 155 case Connected: 156 changeState(Request); 157 goto case Request; 158 159 case Request: 160 if (request_.empty) { 161 auto app = appender!string; 162 163 app.put("GET "); 164 app.put(url_.path.empty ? "/" : url_.path); 165 if (!url_.query.empty) { 166 app.put("?"); 167 app.put(url_.query); 168 } 169 app.put(" HTTP/1.1\r\nHost: "); 170 app.put(url_.host); 171 app.put("\r\n\r\n"); 172 173 request_ = cast(ubyte[])app.data.idup; 174 } 175 176 try { 177 auto result = socket_.send(request_); 178 179 if (result == Socket.ERROR) { 180 if ((Now - testStart_).total!"msecs" >= timeoutMS_) { 181 changeState(Failure); 182 goto case Failure; 183 } 184 break; 185 } 186 187 received_.length = 0; 188 189 changeState(Requesting); 190 goto case Requesting; 191 } catch { 192 changeState(Failure); 193 goto case Failure; 194 } 195 196 case Requesting: 197 ubyte[32] buf; 198 199 auto result = socket_.receive(buf); 200 if (result > 0) { 201 received_ ~= buf[0..result]; 202 if (received_.length > 12) { 203 changeState(Requested); 204 goto case Requested; 205 } 206 } else if (result == Socket.ERROR) { 207 if ((Now - testStart_).total!"msecs" >= timeoutMS_) { 208 changeState(Failure); 209 goto case Failure; 210 } 211 break; 212 } else if (result == 0) { 213 changeState(Requested); 214 goto case Requested; 215 } 216 break; 217 218 case Requested: 219 if (statusOK()) { 220 changeState(Success); 221 goto case Success; 222 } 223 changeState(Failure); 224 goto case Failure; 225 226 case Success: 227 close(); 228 try_ = 0; 229 230 changeState(Idle); 231 goto case Idle; 232 233 case Failure: 234 close(); 235 ++try_; 236 237 if ((try_ <= retryCount_) || ((Now - monitorStart_).total!"msecs" <= graceMS_)) { 238 changeState(GraceFailure); 239 goto case GraceFailure; 240 } 241 break; 242 243 case GraceFailure: 244 if ((Now - monitorStart_).total!"msecs" <= graceMS_) 245 try_ = 0; 246 247 changeState(Idle); 248 goto case Idle; 249 } 250 } 251 252 private bool statusOK() { 253 if (received_.length <= 12) 254 return false; 255 256 257 if ((received_.ptr[0] != 'H') || (received_.ptr[1] != 'T') || (received_.ptr[2] != 'T') || (received_.ptr[3] != 'P') || (received_.ptr[4] != '/')) 258 return false; 259 260 if (received_.ptr[8] != ' ') 261 return false; 262 263 if (received_.ptr[9] == '5') 264 return false; 265 return true; 266 } 267 268 @property State state() { 269 return state_; 270 } 271 272 private: 273 URL url_; 274 275 size_t timeoutMS_; 276 size_t intervalMS_; 277 size_t graceMS_; 278 size_t retryCount_; 279 size_t try_; 280 281 ubyte[] request_; 282 ubyte[] received_; 283 284 SysTime monitorStart_; 285 SysTime testStart_; 286 287 State state_ = State.Ready; 288 Socket socket_; 289 }