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) { 89 url_ = URL.parse(url); 90 91 close(); 92 try_ = 0; 93 94 changeState(State.Ready); 95 monitorStart_ = Now; 96 } 97 98 private void close() { 99 if (socket_ !is null) { 100 socket_.shutdown(SocketShutdown.SEND); 101 socket_.close(); 102 103 destroy(socket_); 104 socket_ = null; 105 106 GC.collect(); 107 } 108 } 109 110 private void changeState(State state) { 111 state_ = state; 112 } 113 114 void update() { 115 final switch (state_) with (State) { 116 case Reset: 117 break; 118 119 case Ready: 120 changeState(Connect); 121 goto case Connect; 122 123 case Idle: 124 if (((Now - testStart_).total!"msecs" >= intervalMS_)) { 125 changeState(Connect); 126 goto case Connect; 127 } 128 break; 129 130 case Connect: 131 assert(socket_ is null); 132 try { 133 testStart_ = Now; 134 socket_ = new TcpSocket(); 135 socket_.blocking = false; 136 137 socket_.connect(new InternetAddress(url_.host, url_.port)); 138 139 changeState(Connecting); 140 goto case Connecting; 141 } catch (Throwable e) { 142 writeln(e.toString); 143 changeState(Failure); 144 goto case Failure; 145 } 146 147 case Connecting: 148 changeState(Connected); 149 goto case Connected; 150 151 case Connected: 152 changeState(Request); 153 goto case Request; 154 155 case Request: 156 if (request_.empty) { 157 auto app = appender!string; 158 159 app.put("GET "); 160 app.put(url_.path.empty ? "/" : url_.path); 161 if (!url_.query.empty) { 162 app.put("?"); 163 app.put(url_.query); 164 } 165 app.put(" HTTP/1.1\r\n\r\n"); 166 167 request_ = cast(ubyte[])app.data.idup; 168 } 169 170 try { 171 auto result = socket_.send(request_); 172 173 if (result == Socket.ERROR) { 174 if ((Now - testStart_).total!"msecs" >= timeoutMS_) { 175 changeState(Failure); 176 goto case Failure; 177 } 178 break; 179 } 180 181 received_.length = 0; 182 183 changeState(Requesting); 184 goto case Requesting; 185 } catch { 186 changeState(Failure); 187 goto case Failure; 188 } 189 190 case Requesting: 191 ubyte[32] buf; 192 193 auto result = socket_.receive(buf); 194 if (result > 0) { 195 received_ ~= buf[0..result]; 196 if (received_.length > 12) { 197 changeState(Requested); 198 goto case Requested; 199 } 200 } else if (result == Socket.ERROR) { 201 if ((Now - testStart_).total!"msecs" >= timeoutMS_) { 202 changeState(Failure); 203 goto case Failure; 204 } 205 break; 206 } else if (result == 0) { 207 changeState(Requested); 208 goto case Requested; 209 } 210 break; 211 212 case Requested: 213 if (statusOK()) { 214 changeState(Success); 215 goto case Success; 216 } 217 changeState(Failure); 218 goto case Failure; 219 220 case Success: 221 close(); 222 try_ = 0; 223 224 changeState(Idle); 225 goto case Idle; 226 227 case Failure: 228 close(); 229 ++try_; 230 231 if ((try_ <= retryCount_) || ((Now - monitorStart_).total!"msecs" <= graceMS_)) { 232 changeState(GraceFailure); 233 goto case GraceFailure; 234 } 235 break; 236 237 case GraceFailure: 238 if ((Now - monitorStart_).total!"msecs" <= graceMS_) 239 try_ = 0; 240 241 changeState(Idle); 242 goto case Idle; 243 } 244 } 245 246 private bool statusOK() { 247 if (received_.length <= 12) 248 return false; 249 250 if ((received_.ptr[0] != 'H') || (received_.ptr[1] != 'T') || (received_.ptr[2] != 'T') || (received_.ptr[3] != 'P') || (received_.ptr[4] != '/')) 251 return false; 252 253 if (received_.ptr[8] != ' ') 254 return false; 255 256 if (received_.ptr[9] == '5') 257 return false; 258 return true; 259 } 260 261 @property State state() { 262 return state_; 263 } 264 265 private: 266 URL url_; 267 268 size_t timeoutMS_ = 2500; 269 size_t intervalMS_ = 5000; 270 size_t graceMS_ = 25000; 271 size_t retryCount_ = 3; 272 size_t try_ = 0; 273 274 ubyte[] request_; 275 ubyte[] received_; 276 277 SysTime monitorStart_; 278 SysTime testStart_; 279 280 State state_ = State.Ready; 281 Socket socket_; 282 }