|
| 1 | +#if !NETSTANDARD1_5 |
| 2 | +using MasterDevs.ChromeDevTools.Serialization; |
| 3 | +using Newtonsoft.Json; |
| 4 | +using System; |
| 5 | +using System.Collections.Concurrent; |
| 6 | +using System.Linq; |
| 7 | +using System.Threading; |
| 8 | +using System.Threading.Tasks; |
| 9 | +using WebSocket4Net; |
| 10 | + |
| 11 | +namespace MasterDevs.ChromeDevTools |
| 12 | +{ |
| 13 | + public class ChromeSession : IChromeSession |
| 14 | + { |
| 15 | + private readonly string _endpoint; |
| 16 | + private readonly ConcurrentDictionary<string, ConcurrentBag<Action<object>>> _handlers = new ConcurrentDictionary<string, ConcurrentBag<Action<object>>>(); |
| 17 | + private ICommandFactory _commandFactory; |
| 18 | + private IEventFactory _eventFactory; |
| 19 | + private ManualResetEvent _openEvent = new ManualResetEvent(false); |
| 20 | + private ManualResetEvent _publishEvent = new ManualResetEvent(false); |
| 21 | + private ConcurrentDictionary<long, ManualResetEventSlim> _requestWaitHandles = new ConcurrentDictionary<long, ManualResetEventSlim>(); |
| 22 | + private ICommandResponseFactory _responseFactory; |
| 23 | + private ConcurrentDictionary<long, ICommandResponse> _responses = new ConcurrentDictionary<long, ICommandResponse>(); |
| 24 | + private WebSocket _webSocket; |
| 25 | + private static object _Lock = new object(); |
| 26 | + |
| 27 | + public event Action<string> UnknownMessageReceived; |
| 28 | + public event Action<byte[]> UnknownDataReceived; |
| 29 | + |
| 30 | + public ChromeSession(string endpoint, ICommandFactory commandFactory, ICommandResponseFactory responseFactory, IEventFactory eventFactory) |
| 31 | + { |
| 32 | + _endpoint = endpoint; |
| 33 | + _commandFactory = commandFactory; |
| 34 | + _responseFactory = responseFactory; |
| 35 | + _eventFactory = eventFactory; |
| 36 | + } |
| 37 | + |
| 38 | + public void Dispose() |
| 39 | + { |
| 40 | + if (null == _webSocket) return; |
| 41 | + if (_webSocket.State == WebSocketState.Open) |
| 42 | + { |
| 43 | + _webSocket.Close(); |
| 44 | + } |
| 45 | + _webSocket.Dispose(); |
| 46 | + } |
| 47 | + |
| 48 | + private void EnsureInit() |
| 49 | + { |
| 50 | + if (null == _webSocket) |
| 51 | + { |
| 52 | + lock (_Lock) |
| 53 | + { |
| 54 | + if (null == _webSocket) |
| 55 | + { |
| 56 | + Init().Wait(); |
| 57 | + } |
| 58 | + } |
| 59 | + } |
| 60 | + } |
| 61 | + |
| 62 | + private Task Init() |
| 63 | + { |
| 64 | + _openEvent.Reset(); |
| 65 | + |
| 66 | + _webSocket = new WebSocket(_endpoint); |
| 67 | + _webSocket.EnableAutoSendPing = false; |
| 68 | + _webSocket.Opened += WebSocket_Opened; |
| 69 | + _webSocket.MessageReceived += WebSocket_MessageReceived; |
| 70 | + _webSocket.Error += WebSocket_Error; |
| 71 | + _webSocket.Closed += WebSocket_Closed; |
| 72 | + _webSocket.DataReceived += WebSocket_DataReceived; |
| 73 | + |
| 74 | + _webSocket.Open(); |
| 75 | + return Task.Run(() => |
| 76 | + { |
| 77 | + _openEvent.WaitOne(); |
| 78 | + }); |
| 79 | + } |
| 80 | + |
| 81 | + public Task<ICommandResponse> SendAsync<T>(CancellationToken cancellationToken) |
| 82 | + { |
| 83 | + var command = _commandFactory.Create<T>(); |
| 84 | + return SendCommand(command, cancellationToken); |
| 85 | + } |
| 86 | + |
| 87 | + public Task<CommandResponse<T>> SendAsync<T>(ICommand<T> parameter, CancellationToken cancellationToken) |
| 88 | + { |
| 89 | + var command = _commandFactory.Create(parameter); |
| 90 | + var task = SendCommand(command, cancellationToken); |
| 91 | + return CastTaskResult<ICommandResponse, CommandResponse<T>>(task); |
| 92 | + } |
| 93 | + |
| 94 | + private Task<TDerived> CastTaskResult<TBase, TDerived>(Task<TBase> task) where TDerived: TBase, new() |
| 95 | + { |
| 96 | + var tcs = new TaskCompletionSource<TDerived>(); |
| 97 | + task.ContinueWith(t => { |
| 98 | + if (t.Result is TDerived) |
| 99 | + tcs.SetResult((TDerived)t.Result); |
| 100 | + else |
| 101 | + tcs.SetResult(new TDerived()); |
| 102 | + //if (t.Result is TDerived) |
| 103 | + // tcs.SetResult((TDerived)t.Result); |
| 104 | + }, TaskContinuationOptions.OnlyOnRanToCompletion); |
| 105 | + task.ContinueWith(t => tcs.SetException(t.Exception.InnerExceptions), |
| 106 | + TaskContinuationOptions.OnlyOnFaulted); |
| 107 | + task.ContinueWith(t => tcs.SetCanceled(), |
| 108 | + TaskContinuationOptions.OnlyOnCanceled); |
| 109 | + return tcs.Task; |
| 110 | + } |
| 111 | + |
| 112 | + public void Subscribe<T>(Action<T> handler) where T : class |
| 113 | + { |
| 114 | + var handlerType = typeof(T); |
| 115 | + var handlerForBag = new Action<object>(obj => handler((T)obj)); |
| 116 | + _handlers.AddOrUpdate(handlerType.FullName, |
| 117 | + (m) => new ConcurrentBag<Action<object>>(new [] { handlerForBag }), |
| 118 | + (m, currentBag) => |
| 119 | + { |
| 120 | + currentBag.Add(handlerForBag); |
| 121 | + return currentBag; |
| 122 | + }); |
| 123 | + } |
| 124 | + |
| 125 | + private void HandleEvent(IEvent evnt) |
| 126 | + { |
| 127 | + if (null == evnt |
| 128 | + || null == evnt) |
| 129 | + { |
| 130 | + return; |
| 131 | + } |
| 132 | + var type = evnt.GetType().GetGenericArguments().FirstOrDefault(); |
| 133 | + if (null == type) |
| 134 | + { |
| 135 | + return; |
| 136 | + } |
| 137 | + var handlerKey = type.FullName; |
| 138 | + ConcurrentBag<Action<object>> handlers = null; |
| 139 | + if (_handlers.TryGetValue(handlerKey, out handlers)) |
| 140 | + { |
| 141 | + var localHandlers = handlers.ToArray(); |
| 142 | + foreach (var handler in localHandlers) |
| 143 | + { |
| 144 | + ExecuteHandler(handler, evnt); |
| 145 | + } |
| 146 | + } |
| 147 | + } |
| 148 | + |
| 149 | + private void ExecuteHandler(Action<object> handler, dynamic evnt) |
| 150 | + { |
| 151 | + if (evnt.GetType().GetGenericTypeDefinition() == typeof(Event<>)) |
| 152 | + { |
| 153 | + handler(evnt.Params); |
| 154 | + } else |
| 155 | + { |
| 156 | + handler(evnt); |
| 157 | + } |
| 158 | + } |
| 159 | + |
| 160 | + private void HandleResponse(ICommandResponse response) |
| 161 | + { |
| 162 | + if (null == response) return; |
| 163 | + ManualResetEventSlim requestMre; |
| 164 | + if (_requestWaitHandles.TryGetValue(response.Id, out requestMre)) |
| 165 | + { |
| 166 | + _responses.AddOrUpdate(response.Id, id => response, (key, value) => response); |
| 167 | + requestMre.Set(); |
| 168 | + } |
| 169 | + else |
| 170 | + { |
| 171 | + // in the case of an error, we don't always get the request Id back :( |
| 172 | + // if there is only one pending requests, we know what to do ... otherwise |
| 173 | + if (1 == _requestWaitHandles.Count) |
| 174 | + { |
| 175 | + var requestId = _requestWaitHandles.Keys.First(); |
| 176 | + _requestWaitHandles.TryGetValue(requestId, out requestMre); |
| 177 | + _responses.AddOrUpdate(requestId, id => response, (key, value) => response); |
| 178 | + requestMre.Set(); |
| 179 | + } |
| 180 | + } |
| 181 | + } |
| 182 | + |
| 183 | + private Task<ICommandResponse> SendCommand(Command command, CancellationToken cancellationToken) |
| 184 | + { |
| 185 | + var settings = new JsonSerializerSettings |
| 186 | + { |
| 187 | + ContractResolver = new MessageContractResolver(), |
| 188 | + NullValueHandling = NullValueHandling.Ignore, |
| 189 | + }; |
| 190 | + var requestString = JsonConvert.SerializeObject(command, settings); |
| 191 | + var requestResetEvent = new ManualResetEventSlim(false); |
| 192 | + _requestWaitHandles.AddOrUpdate(command.Id, requestResetEvent, (id, r) => requestResetEvent); |
| 193 | + return Task.Run(() => |
| 194 | + { |
| 195 | + EnsureInit(); |
| 196 | + _webSocket.Send(requestString); |
| 197 | + requestResetEvent.Wait(cancellationToken); |
| 198 | + ICommandResponse response = null; |
| 199 | + _responses.TryRemove(command.Id, out response); |
| 200 | + _requestWaitHandles.TryRemove(command.Id, out requestResetEvent); |
| 201 | + return response; |
| 202 | + }); |
| 203 | + } |
| 204 | + |
| 205 | + private bool TryGetCommandResponse(byte[] data, out ICommandResponse response) |
| 206 | + { |
| 207 | + response = _responseFactory.Create(data); |
| 208 | + return null != response; |
| 209 | + } |
| 210 | + |
| 211 | + private bool TryGetCommandResponse(string message, out ICommandResponse response) |
| 212 | + { |
| 213 | + response = _responseFactory.Create(message); |
| 214 | + return null != response; |
| 215 | + } |
| 216 | + |
| 217 | + private bool TryGetEvent(byte[] data, out IEvent evnt) |
| 218 | + { |
| 219 | + evnt = _eventFactory.Create(data); |
| 220 | + return null != evnt; |
| 221 | + } |
| 222 | + |
| 223 | + private bool TryGetEvent(string message, out IEvent evnt) |
| 224 | + { |
| 225 | + evnt = _eventFactory.Create(message); |
| 226 | + return null != evnt; |
| 227 | + } |
| 228 | + |
| 229 | + private void WebSocket_Closed(object sender, EventArgs e) |
| 230 | + { |
| 231 | + } |
| 232 | + |
| 233 | + private void WebSocket_DataReceived(object sender, DataReceivedEventArgs e) |
| 234 | + { |
| 235 | + ICommandResponse response; |
| 236 | + if (TryGetCommandResponse(e.Data, out response)) |
| 237 | + { |
| 238 | + HandleResponse(response); |
| 239 | + return; |
| 240 | + } |
| 241 | + IEvent evnt; |
| 242 | + if (TryGetEvent(e.Data, out evnt)) |
| 243 | + { |
| 244 | + HandleEvent(evnt); |
| 245 | + return; |
| 246 | + } |
| 247 | + UnknownDataReceived?.Invoke(e.Data); |
| 248 | + } |
| 249 | + |
| 250 | + private void WebSocket_Error(object sender, SuperSocket.ClientEngine.ErrorEventArgs e) |
| 251 | + { |
| 252 | + throw e.Exception; |
| 253 | + } |
| 254 | + |
| 255 | + private void WebSocket_MessageReceived(object sender, MessageReceivedEventArgs e) |
| 256 | + { |
| 257 | + ICommandResponse response; |
| 258 | + if (TryGetCommandResponse(e.Message, out response)) |
| 259 | + { |
| 260 | + HandleResponse(response); |
| 261 | + return; |
| 262 | + } |
| 263 | + IEvent evnt; |
| 264 | + if (TryGetEvent(e.Message, out evnt)) |
| 265 | + { |
| 266 | + HandleEvent(evnt); |
| 267 | + return; |
| 268 | + } |
| 269 | + UnknownMessageReceived?.Invoke(e.Message); |
| 270 | + } |
| 271 | + |
| 272 | + private void WebSocket_Opened(object sender, EventArgs e) |
| 273 | + { |
| 274 | + _openEvent.Set(); |
| 275 | + } |
| 276 | + } |
| 277 | +} |
| 278 | +#endif |
0 commit comments