public static XmlDocument makeRequest(string url, string method, string body = null) { var request = WebRequest.CreateHttp(url); request.Method = method; var header = new MKMAuth.OAuthHeader(); request.Headers.Add(HttpRequestHeader.Authorization, header.getAuthorizationHeader(method, url)); request.Method = method; if (body != null) { request.ServicePoint.Expect100Continue = false; request.ContentLength = body.Length; request.ContentType = "text/xml"; var writer = new StreamWriter(request.GetRequestStream()); writer.Write(body); writer.Close(); } var response = request.GetResponse() as HttpWebResponse; var doc = new XmlDocument(); doc.Load(response.GetResponseStream()); return(doc); }
private static System.DateTime denyTime; // to know when denyAdditionalRequests was switched, if we pass to another day, reset it /// <summary> /// Makes a request from MKM's API. /// If the daily request limit has been reached, does not send the request and instead throws an exception. /// </summary> /// <param name="url">The http URL of the API.</param> /// <param name="method">The name of the request method (PUT, GET, etc.).</param> /// <param name="body">The body containing parameters of the method if applicable.</param> /// <returns>Document containing the response from MKM. In some cases this can empty (when the response is "nothing matches your request").</returns> /// <exception cref="HttpListenerException">429 - Too many requests. Wait for 0:00 CET for request counter to reset.</exception> /// <exception cref="APIProcessingExceptions">Many different network-based exceptions.</exception> public static XmlDocument makeRequest(string url, string method, string body = null) { // throw the exception ourselves to prevent sending requests to MKM that would end with this error // because MKM tends to revoke the user's app token if it gets too many requests above the limit // the 429 code is the same MKM uses for this error if (denyAdditionalRequests) { // MKM resets the counter at 0:00 CET. CET is two hours ahead of UCT, so if it is after 22:00 of the same day // the denial was triggered, that means the 0:00 CET has passed and we can reset the deny if (System.DateTime.UtcNow.Date == denyTime.Date && System.DateTime.UtcNow.Hour < 22) { throw new HttpListenerException(429, "Too many requests. Wait for 0:00 CET for request counter to reset."); } else { denyAdditionalRequests = false; } } XmlDocument doc = new XmlDocument(); var request = WebRequest.CreateHttp(url); request.Method = method; var header = new MKMAuth.OAuthHeader(); request.Headers.Add(HttpRequestHeader.Authorization, header.getAuthorizationHeader(method, url)); request.Method = method; if (body != null) { request.ServicePoint.Expect100Continue = false; request.ContentLength = body.Length; request.ContentType = "text/xml"; var writer = new StreamWriter(request.GetRequestStream()); writer.Write(body); writer.Close(); } var response = request.GetResponse() as HttpWebResponse; // just for checking EoF, it is not accessible directly from the Stream object // Empty streams can be returned for example for article fetches that result in 0 matches (happens regularly when e.g. seeking nonfoils in foil-only promo sets). // Passing empty stream to doc.Load causes exception and also sometimes seems to screw up the XML parser // even when the exception is handled and it then causes problems for subsequent calls => first check if the stream is empty StreamReader s = new StreamReader(response.GetResponseStream()); if (!s.EndOfStream) { doc.Load(s); } s.Close(); int requestCount = int.Parse(response.Headers.Get("X-Request-Limit-Count")); int requestLimit = int.Parse(response.Headers.Get("X-Request-Limit-Max")); if (requestCount >= requestLimit) { denyAdditionalRequests = true; denyTime = System.DateTime.UtcNow; } MainView.Instance.Invoke(new MainView.updateRequestCountCallback(MainView.Instance.updateRequestCount), requestCount, requestLimit); return(doc); }
/// <summary> /// Makes a request from MKM's API. /// If the daily request limit has been reached, does not send the request and instead throws an exception. /// </summary> /// <param name="url">The http URL of the API.</param> /// <param name="method">The name of the request method (PUT, GET, etc.).</param> /// <param name="body">The body containing parameters of the method if applicable.</param> /// <returns>Document containing the response from MKM. In some cases this can empty (when the response is "nothing matches your request").</returns> /// <exception cref="HttpListenerException">429 - Too many requests. Wait for 0:00 CET for request counter to reset.</exception> /// <exception cref="APIProcessingExceptions">Many different network-based exceptions.</exception> public static XmlDocument makeRequest(string url, string method, string body = null) { // throw the exception ourselves to prevent sending requests to MKM that would end with this error // because MKM tends to revoke the user's app token if it gets too many requests above the limit // the 429 code is the same MKM uses for this error if (denyAdditionalRequests) { // MKM resets the counter at 0:00 CET. CET is two hours ahead of UCT, so if it is after 22:00 of the same day // the denial was triggered, that means the 0:00 CET has passed and we can reset the deny if (DateTime.UtcNow.Date == denyTime.Date && DateTime.UtcNow.Hour < 22) { throw new HttpListenerException(429, "Too many requests. Wait for 0:00 CET for request counter to reset."); } else { denyAdditionalRequests = false; } } // enforce the maxRequestsPerMinute limit - technically it's just an approximation as the requests // can arrive to MKM with some delay, but it should be close enough var now = DateTime.Now; while (requestTimes.Count > 0 && (now - requestTimes.Peek()).TotalSeconds > 60) { requestTimes.Dequeue();// keep only times of requests in the past 60 seconds } if (requestTimes.Count >= maxRequestsPerMinute) { // wait until 60.01 seconds passed since the oldest request // we know (now - peek) is <= 60, otherwise it would get dequeued above, // so we are passing a positive number to sleep System.Threading.Thread.Sleep( 60010 - (int)(now - requestTimes.Peek()).TotalMilliseconds); requestTimes.Dequeue(); } requestTimes.Enqueue(DateTime.Now); XmlDocument doc = new XmlDocument(); var request = WebRequest.CreateHttp(url); request.Method = method; request.Headers.Add(HttpRequestHeader.Authorization, header.getAuthorizationHeader(method, url)); request.Method = method; if (body != null) { request.ServicePoint.Expect100Continue = false; request.ContentLength = System.Text.Encoding.UTF8.GetByteCount(body); request.ContentType = "text/xml"; var writer = new StreamWriter(request.GetRequestStream()); writer.Write(body); writer.Close(); } var response = request.GetResponse() as HttpWebResponse; // just for checking EoF, it is not accessible directly from the Stream object // Empty streams can be returned for example for article fetches that result in 0 matches (happens regularly when e.g. seeking nonfoils in foil-only promo sets). // Passing empty stream to doc.Load causes exception and also sometimes seems to screw up the XML parser // even when the exception is handled and it then causes problems for subsequent calls => first check if the stream is empty StreamReader s = new StreamReader(response.GetResponseStream()); if (!s.EndOfStream) { doc.Load(s); } s.Close(); int requestCount = int.Parse(response.Headers.Get("X-Request-Limit-Count")); int requestLimit = int.Parse(response.Headers.Get("X-Request-Limit-Max")); if (requestCount >= requestLimit) { denyAdditionalRequests = true; denyTime = System.DateTime.UtcNow; } MainView.Instance.Invoke(new MainView.updateRequestCountCallback(MainView.Instance.UpdateRequestCount), requestCount, requestLimit); return(doc); }