public async Task RateGate() { const int timesPerPeriod = 1; const int ms = 100; const int loops = 5; double msMax = (double)ms * 1.5; double msMin = (double)ms * (1.0 / 1.5); RateGate gate = new RateGate(timesPerPeriod, TimeSpan.FromMilliseconds(ms)); if (!(await gate.WaitToProceedAsync(0))) { throw new APIException("Rate gate should have allowed immediate access to first attempt"); } for (int i = 0; i < loops; i++) { Stopwatch timer = Stopwatch.StartNew(); await gate.WaitToProceedAsync(); timer.Stop(); if (i > 0) { // check for too much elapsed time with a little fudge Assert.IsTrue(timer.Elapsed.TotalMilliseconds <= msMax, "Rate gate took too long to wait in between calls: " + timer.Elapsed.TotalMilliseconds + "ms"); Assert.IsTrue(timer.Elapsed.TotalMilliseconds >= msMin, "Rate gate took too little to wait in between calls: " + timer.Elapsed.TotalMilliseconds + "ms"); } } }
protected async Task <ExchangeResponse> GetResponse(ExchangeRequestBase request) { var relativeUrlForURL = request.RequestUrl; var baseURI = IsSandbox ? API_SANDBOX_ENDPOINT_URL : API_ENDPOINT_URL; var absoluteUri = new Uri(baseURI, relativeUrlForURL); var uriBuilder = new UriBuilder(absoluteUri); uriBuilder.Port = -1; // add query parameters var requestCasted = request as ExchangePageableRequestBase; var nvc = HttpUtility.ParseQueryString(string.Empty); if (request is ExchangePageableRequestBase) { if (requestCasted.afterCursor != null) { nvc["after"] = requestCasted.afterCursor; } if (requestCasted.RecordCount != null) { nvc["limit"] = requestCasted.RecordCount.Value.ToString(); } //if (requestCasted.ZeroBasedCursor < 0) //{ // nvc["before"] = (Math.Abs(requestCasted.ZeroBasedCursor-1)).ToString(); // change to 1 based // if (requestCasted.RecordCount != null) // nvc["limit"] = requestCasted.RecordCount.Value.ToString(); //} //else if (requestCasted.ZeroBasedCursor > 0) //{ // nvc["after"] = (requestCasted.ZeroBasedCursor+1).ToString(); // change to 1 based // if (requestCasted.RecordCount != null) // nvc["limit"] = requestCasted.RecordCount.Value.ToString(); //} // else it's zero so no need to put any query parameter } if (request is GetPersonalOrdersRequest) { var requestCasted2 = request as GetPersonalOrdersRequest; if (requestCasted2.Status != null) { foreach (var status in requestCasted2.Status) { nvc.Add("status", status); } } } if (request is CancelAllPersonalOrdersRequest) { var requestCasted3 = request as CancelAllPersonalOrdersRequest; if (requestCasted3.product_id != null) { nvc["product_id"] = requestCasted3.product_id; } } if (nvc.Keys.Count > 0) { uriBuilder.Query = nvc.ToString(); } var body = request.RequestBody; var method = request.Method; var url = uriBuilder.ToString(); var relativeUrlForSignature = baseURI.MakeRelativeUri(uriBuilder.Uri).ToString(); await rateGatePolling.WaitToProceedAsync(); // rate limit prior to TimeStamp being generated using (var httpClient = new HttpClient()) { if (_authContainer != null) { // authenticated get, required for querying account specific data, but optional for public data // Caution: Use the relative URL, *NOT* the absolute one. var signature = _authContainer.ComputeSignature("/" + relativeUrlForSignature, method, body); httpClient.DefaultRequestHeaders.Add("CB-ACCESS-KEY", signature.ApiKey); httpClient.DefaultRequestHeaders.Add("CB-ACCESS-SIGN", signature.Signature); httpClient.DefaultRequestHeaders.Add("CB-ACCESS-TIMESTAMP", signature.TimeStamp); httpClient.DefaultRequestHeaders.Add("CB-ACCESS-PASSPHRASE", signature.Passphrase); } httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(ContentType)); httpClient.DefaultRequestHeaders.Add("User-Agent", "vslee fork of sefbkn.github.io"); HttpResponseMessage response; switch (method) { case "GET": response = await httpClient.GetAsync(url); break; case "POST": var requestBody = new StringContent(body, Encoding.UTF8, "application/json"); response = await httpClient.PostAsync(url, requestBody); break; case "DELETE": response = await httpClient.DeleteAsync(url); break; case "PUT": throw new NotImplementedException("PUT"); default: throw new NotImplementedException("The supplied HTTP method is not supported: " + method ?? "(null)"); } var contentBody = await response.Content.ReadAsStringAsync(); var headers = response.Headers.AsEnumerable(); var statusCode = response.StatusCode; var isSuccess = response.IsSuccessStatusCode; var genericExchangeResponse = new ExchangeResponse(statusCode, isSuccess, headers, contentBody); return(genericExchangeResponse); } }
public async Task SubscribeAsync(bool reConnectOnDisconnect) { var uri = ExchangeClientBase.IsSandbox ? WSS_SANDBOX_ENDPOINT_URL : WSS_ENDPOINT_URL; if (_authContainer != null) // authenticated feed { uri = new Uri(uri, "/users/self/verify"); } cancellationTokenSource = new CancellationTokenSource(); while (!cancellationTokenSource.IsCancellationRequested) { string disconnectReason = ""; try { webSocketClient = new ClientWebSocket(); await webSocketClient.ConnectAsync(uri, cancellationTokenSource.Token); if (webSocketClient.State == System.Net.WebSockets.WebSocketState.Open && !cancellationTokenSource.IsCancellationRequested) { await rateGateRealtime.WaitToProceedAsync(); // don't subscribe at too high of a rate await sendSubscriptionMsgAsync(Products : Products, gdax_Channel : gdax_Channel); // key is product name, value is whether connection was just opened if (webSocketClient.State == System.Net.WebSockets.WebSocketState.Open && !cancellationTokenSource.IsCancellationRequested) { // checking again bc maybe the server disconnected after the subscribe msg was sent // + move to processing subscriptions section below later foreach (var product in Products) { ConnectionOpened?.Invoke(product, gdax_Channel); } } while (webSocketClient.State == System.Net.WebSockets.WebSocketState.Open && !cancellationTokenSource.IsCancellationRequested) { using (var timeoutCTS = new CancellationTokenSource(6500)) // heartbeat every 1000 ms, so give it 5 hearbeat chances using (var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(timeoutCTS.Token, cancellationTokenSource.Token)) using (var stream = new MemoryStream(1024)) { var receiveBuffer = new ArraySegment <byte>(new byte[1024 * 8]); bool timedOut = false; WebSocketReceiveResult webSocketReceiveResult; do { try { webSocketReceiveResult = await webSocketClient.ReceiveAsync(receiveBuffer, linkedTokenSource.Token); } catch (OperationCanceledException) { timedOut = true; disconnectReason = " - stream timed out"; break; } await stream.WriteAsync(receiveBuffer.Array, receiveBuffer.Offset, webSocketReceiveResult.Count, cancellationTokenSource.Token); } while (!webSocketReceiveResult.EndOfMessage && !cancellationTokenSource.IsCancellationRequested); if (!timedOut && !cancellationTokenSource.IsCancellationRequested) { var message = stream.ToArray().Where(b => b != 0).ToArray(); var messageString = Encoding.ASCII.GetString(message, 0, message.Length); if (!String.IsNullOrEmpty(messageString)) { try { var jToken = JToken.Parse(messageString); var typeToken = jToken["type"]; if (typeToken == null) { RealtimeDataError?.Invoke(this, new RealtimeError("null typeToken: + " + Encoding.ASCII.GetString(message, 0, message.Length))); return; // go to next msg } var type = typeToken.Value <string>(); switch (type) { case "subscriptions": // + process initial subscription confirmation // + also for unsubscribe confirmation break; case "received": var rr = new RealtimeReceived(jToken); if (rr.Message != null) { RealtimeDataError?.Invoke(this, rr); } RealtimeReceived?.Invoke(this, rr); break; case "open": var ro = new RealtimeOpen(jToken); if (ro.Message != null) { RealtimeDataError?.Invoke(this, ro); } RealtimeOpen?.Invoke(this, ro); break; case "done": var rd = new RealtimeDone(jToken); if (rd.Message != null) { RealtimeDataError?.Invoke(this, rd); } RealtimeDone?.Invoke(this, rd); break; case "match": var rm = new RealtimeMatch(jToken); if (rm.Message != null) { RealtimeDataError?.Invoke(this, rm); } RealtimeMatch?.Invoke(this, rm); break; case "last_match": var rlm = new RealtimeMatch(jToken); if (rlm.Message != null) { RealtimeDataError?.Invoke(this, rlm); } RealtimeLastMatch?.Invoke(this, rlm); break; case "change": var rc = new RealtimeChange(jToken); if (rc.Message != null) { RealtimeDataError?.Invoke(this, rc); } RealtimeChange?.Invoke(this, rc); break; case "heartbeat": // + should implement this (checking LastTraderId) var hb = new Heartbeat(jToken); Heartbeat?.Invoke(this, hb); break; case "error": RealtimeDataError?.Invoke(this, new RealtimeError(jToken)); break; default: RealtimeDataError?.Invoke(this, new RealtimeError("Unexpected type: " + jToken)); break; } } catch (JsonReaderException e) { RealtimeDataError?.Invoke(this, new RealtimeError( "JsonReaderException: " + e.Message + ":" + messageString)); } } else { RealtimeDataError?.Invoke(this, new RealtimeError("empty message received. Connection state: " + webSocketClient.State + ", linkedToken: " + linkedTokenSource.Token.IsCancellationRequested)); } } } } } } catch (Exception e) { if (e.Message == "The remote party closed the WebSocket connection without completing the close handshake.") // System.Net.WebSockets.WebSocketException { disconnectReason = " - remote closed the WebSocket w/o completing the close handshake"; } else if (e.Message == "Unable to connect to the remote server") // System.Net.WebSockets.WebSocketException { disconnectReason = " - unable to connect to server"; // shorten it a bit await Task.Delay(10000); // if unable to connect, then wait 10 seconds before trying to connect again } else { RealtimeStreamError?.Invoke(this, new RealtimeError("other exception caught: " + e.GetType() + " : " + e.Message)); } } if (!reConnectOnDisconnect) { UnSubscribe(); } foreach (var product in Products) { RealtimeStreamError?.Invoke(this, new RealtimeError("disconnected" + disconnectReason)); ConnectionClosed?.Invoke(product, gdax_Channel); } if (!reConnectOnDisconnect) { break; } } }
/// <summary> /// Don't await this - or it won't return until the subscription ends /// Authenticated feed messages will not increment the sequence number. It is currently not possible to detect if an authenticated feed message was dropped. /// </summary> /// <param name="onMessageReceived"></param> public async Task SubscribeAsync() { if (String.IsNullOrWhiteSpace(ProductString)) { throw new ArgumentNullException("product"); } string requestString; var uri = ExchangeClientBase.IsSandbox ? WSS_SANDBOX_ENDPOINT_URL : WSS_ENDPOINT_URL; if (_authContainer == null) { // unauthenticated feed requestString = String.Format(@"{{""type"": ""subscribe"",""product_id"": ""{0}""}}", ProductString); } else { // authenticated feed var signBlock = _authContainer.ComputeSignature(relativeUrl: "/users/self", method: "GET", body: ""); requestString = String.Format( @"{{""type"": ""subscribe"",""product_id"": ""{0}"",""signature"": ""{1}"",""key"": ""{2}"",""passphrase"": ""{3}"",""timestamp"": ""{4}""}}", ProductString, signBlock.Signature, signBlock.ApiKey, signBlock.Passphrase, signBlock.TimeStamp); uri = new Uri(uri, "/users/self"); } var requestBytes = UTF8Encoding.UTF8.GetBytes(requestString); var subscribeRequest = new ArraySegment <byte>(requestBytes); var cancellationToken = cancellationTokenSource.Token; while (!cancellationToken.IsCancellationRequested) { try { using (var webSocketClient = new ClientWebSocket()) { await webSocketClient.ConnectAsync(uri, cancellationToken); if (webSocketClient.State == WebSocketState.Open) { await rateGateRealtime.WaitToProceedAsync(); // don't subscribe at too high of a rate await webSocketClient.SendAsync(subscribeRequest, WebSocketMessageType.Text, true, cancellationToken); while (webSocketClient.State == WebSocketState.Open) { string jsonResponse = "<not assigned>"; try { var receiveBuffer = new ArraySegment <byte>(new byte[1024 * 1024 * 5]); // 5MB buffer var webSocketReceiveResult = await webSocketClient.ReceiveAsync(receiveBuffer, cancellationToken); if (webSocketReceiveResult.Count == 0) { continue; } jsonResponse = Encoding.UTF8.GetString(receiveBuffer.Array, 0, webSocketReceiveResult.Count); var jToken = JToken.Parse(jsonResponse); var typeToken = jToken["type"]; if (typeToken == null) { OnRealtimeError(new RealtimeError("null typeToken: + " + jsonResponse)); continue; // go to next msg } var type = typeToken.Value <string>(); switch (type) { case "received": EventHandler <RealtimeReceived> receivedHandler = RealtimeReceived; receivedHandler?.Invoke(this, new RealtimeReceived(jToken)); break; case "open": EventHandler <RealtimeOpen> openHandler = RealtimeOpen; openHandler?.Invoke(this, new RealtimeOpen(jToken)); break; case "done": EventHandler <RealtimeDone> doneHandler = RealtimeDone; doneHandler?.Invoke(this, new RealtimeDone(jToken)); break; case "match": EventHandler <RealtimeMatch> matchHandler = RealtimeMatch; matchHandler?.Invoke(this, new RealtimeMatch(jToken)); break; case "change": EventHandler <RealtimeChange> changeHandler = RealtimeChange; changeHandler?.Invoke(this, new RealtimeChange(jToken)); break; case "heartbeat": // + should implement this break; case "error": OnRealtimeError(new RealtimeError(jToken)); break; default: break; } } catch (Newtonsoft.Json.JsonReaderException e) { // Newtonsoft.Json.JsonReaderException occurred Message = Unexpected end of content while loading JObject.Path 'time' OnRealtimeError(new RealtimeError(e.Message + ", Msg: " + jsonResponse)); // probably malformed message, so just go to the next msg } } } } } catch (System.Net.WebSockets.WebSocketException e) { // System.Net.WebSockets.WebSocketException: 'The remote party closed the WebSocket connection without completing the close handshake.' OnRealtimeError(new RealtimeError(e.Message)); // probably just disconnected, so loop back and reconnect again } catch (System.OperationCanceledException e) { // System.OperationCanceledException: 'The operation was canceled.' OnRealtimeError(new RealtimeError("Cancellation successful: " + e.Message)); break; // exit loop } } }
public async Task <Message> OnSendingAsync(Message envelope, CancellationToken cancellationToken) { await _rateGate.WaitToProceedAsync(cancellationToken); return(envelope); }