/// <summary> /// Handles client requests and routes to GET or POST methods accordingly /// </summary> /// <param name="context"></param> public void ProcessRequest(HttpContext context) { #if DEBUG System.Diagnostics.Stopwatch stopWatch = new System.Diagnostics.Stopwatch(); stopWatch.Start(); logTime.Info("Start " + System.Reflection.MethodBase.GetCurrentMethod().Name); #endif try { string cacheKey = ""; // Key to request stored in cache ResponseBucket cacheResponse; // Request stored in cache string postData = ""; // Data part of POST request (contains JSON query) // Negotiate with the request limiter (if enabled) if (_requestLimiter != null) { if (!_requestLimiter.ClientLimitOK(context.Request)) { // Deny request // see 409 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html context.Response.AppendHeader("Retry-After", _requestLimiter.LimiterTimeSpan.ToString()); throw new HttpException(429, "429 - Too many requests in too short timeframe. Please try again later."); } } if (Settings.Current.EnableCORS) { // Enable CORS (Cross-Origin-Resource-Sharing) context.Response.AppendHeader("Access-Control-Allow-Origin", "*"); // Enable Preflight requests if (context.Request.HttpMethod == "OPTIONS") { context.Response.AppendHeader("Access-Control-Allow-Methods", "GET, POST"); context.Response.AppendHeader("Access-Control-Allow-Headers", "Content-Type"); //context.Response.End(); return; } } // Create cache key by combining URL and query strings if (context.Request.HttpMethod == "POST") { using (var stream = new StreamReader(context.Request.InputStream, context.Request.ContentEncoding)) { postData = stream.ReadToEnd(); } } cacheKey = ApiCache.CreateKey(context.Request.Url.AbsoluteUri + postData); // Try to get request from cache cacheResponse = ApiCache.Current.Fetch(cacheKey); if (cacheResponse != null) { if (cacheResponse.Url.Equals(context.Request.Url.AbsoluteUri) && cacheResponse.PostData.Equals(postData)) { // Use cached object context.Send(cacheResponse, false); if (context.Request.HttpMethod == "POST") { _usageLogger.Info(String.Format("url={0}, type=data, caller={3}, cached=true, format={1}, matrix-size={2}", context.Request.RawUrl, cacheResponse.ContentType, "?", context.Request.UserHostAddress)); } else { _usageLogger.Info(String.Format("url={0}, type=metadata, caller={1}, cached=true, format=?, matrix-size=?", context.Request.RawUrl, context.Request.UserHostAddress)); } #if DEBUG stopWatch.Stop(); logTime.InfoFormat(System.Reflection.MethodBase.GetCurrentMethod().Name + " Done from cahce in ms = {0}", stopWatch.ElapsedMilliseconds); #endif return; } } if (_apiCacheLogger.IsDebugEnabled) { _apiCacheLogger.DebugFormat("Key {0} not found in cache, creating response from backend.", cacheKey); } // Request object to be stored in cache cacheResponse = new ResponseBucket(); cacheResponse.Key = cacheKey; cacheResponse.Url = context.Request.Url.AbsoluteUri; cacheResponse.PostData = postData; List <string> routeParts = null; string language = null; string db = null; // Fetch the route data var routeData = context.Items["RouteData"] as RouteData; if (routeData.Values["language"] != null) { language = routeData.Values["language"].ToString(); } if (routeData.Values["path"] != null) { routeParts = routeData.Values["path"].ToString().Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries).ToList(); db = routeParts.First(); routeParts.RemoveAt(0); } // Parse query string var options = new Options(context.Request.QueryString); var queryKeys = context.Request.QueryString.ToString().Split('&'); if (queryKeys.Contains("config")) { var obj = new { maxValues = Settings.Current.MaxValues, maxCells = Settings.Current.FetchCellLimit, maxCalls = Settings.Current.LimiterRequests, timeWindow = Settings.Current.LimiterTimeSpan, CORS = Settings.Current.EnableCORS }; cacheResponse.ContentType = "application/json; charset=" + System.Text.Encoding.UTF8.WebName; cacheResponse.ResponseData = context.Response.ContentEncoding.GetBytes(obj.ToJSON(options.PrettyPrint)); context.Send(cacheResponse, true); _usageLogger.Info(String.Format("url={0}, type=config, caller={1}, cached=false", context.Request.RawUrl, context.Request.UserHostAddress)); #if DEBUG stopWatch.Stop(); logTime.InfoFormat(System.Reflection.MethodBase.GetCurrentMethod().Name + " Done in ms = {0}", stopWatch.ElapsedMilliseconds); #endif return; } if (ExposedDatabases.DatabaseConfigurations.ContainsKey(language) == false) { throw new ArgumentException("The specified language '" + language + "' does not exist"); } else if (db == null) { var databases = new List <MetaDb>(); foreach (var database in ExposedDatabases.DatabaseConfigurations[language].Keys) { databases.Add(new MetaDb { Id = database, Text = ExposedDatabases.DatabaseConfigurations[language][database].Name }); } if (databases.Count > 0) { cacheResponse.ContentType = "application/json; charset=" + System.Text.Encoding.UTF8.WebName; cacheResponse.ResponseData = context.Response.ContentEncoding.GetBytes(databases.ToJSON(options.PrettyPrint)); context.Send(cacheResponse, true); //context.SendJSON(databases.ToJSON(options.PrettyPrint)); } else { throw new ArgumentException("No databases found in the specified language"); } } else // We have a database specified { if (ExposedDatabases.DatabaseConfigurations[language].ContainsKey(db) == false) { // Database does not exist throw new ArgumentException("The specified database '" + db + "' does not exist"); } else { // Try to get a menu item first string[] strNodePath = (routeParts == null || routeParts.Count == 0) ? new string[] { "" } : routeParts.ToArray(); var menu = PCAxisRepository.GetMenu(db, language, strNodePath); if (menu == null) { //TODO Check exception type throw new Exception("Error while connecting to data source"); } else if ((menu.ID.Menu == "") && (menu.ID.Selection == "")) { // Could not find level in tree - Check if it is a short URL with table if (strNodePath.Length == 1 && strNodePath[0] != "") { var possibleNameOrId = strNodePath[0]; PCAxis.Search.SearchStatusType status; var text = string.Format("{0}:{2} OR {1}:{2}", PCAxis.Search.SearchConstants.SEARCH_FIELD_SEARCHID, PCAxis.Search.SearchConstants.SEARCH_FIELD_TABLEID, possibleNameOrId); var results = PCAxis.Search.SearchManager.Current.Search(db, language, text, out status); if (status == Search.SearchStatusType.Successful && results.Count > 0) { if (_logger.IsDebugEnabled) { if (results.Count > 1) { for (int cnter = 1; cnter < results.Count; cnter++) { if (!String.Equals(results[cnter - 1].Table, results[cnter].Table)) { _logger.DebugFormat("Hmmm, not some result have different table names: {0} differs from {1}", results[cnter - 1].Table, results[cnter].Table); } } } } strNodePath = (results[0].Path + "/" + results[0].Table).Split('/'); //Remove first element in array strNodePath = strNodePath.Where((source, index) => index != 0).ToArray(); routeParts = strNodePath.ToList(); // Get the menu again... menu = PCAxisRepository.GetMenu(db, language, strNodePath); } } } //else //{ var currentItem = menu; //.CurrentItem as Item; if (currentItem.MenuType == typeof(PxMenuItem) && currentItem.HasSubItems) { if (!string.IsNullOrEmpty(options.SearchQuery)) { // Find tables using the search function PCAxis.Search.SearchStatusType status; cacheResponse.ContentType = "application/json; charset=" + System.Text.Encoding.UTF8.WebName; cacheResponse.ResponseData = context.Response.ContentEncoding.GetBytes(GetSearchResult(db, language, options.SearchQuery, options.SearchFilter, routeParts, out status).ToJSON(options.PrettyPrint)); if (status == Search.SearchStatusType.Successful) { context.Send(cacheResponse, false); // Send without caching } else { // Trying to search a non-indexed database... context.SendJSONError(Error("Search not activated", false), 400); _usageLogger.Info(String.Format("url={0}, type=error, caller={1}, cached=false, message=Search not activated", context.Request.RawUrl, context.Request.UserHostAddress)); _logger.Warn(String.Format("Search not activated for {0} - {1}", db, language)); #if DEBUG stopWatch.Stop(); logTime.InfoFormat(System.Reflection.MethodBase.GetCurrentMethod().Name + " Done in ms = {0}", stopWatch.ElapsedMilliseconds); #endif return; } } else { // Current item is a list item - return meta data for this list cacheResponse.ContentType = "application/json; charset=" + System.Text.Encoding.UTF8.WebName; cacheResponse.ResponseData = context.Response.ContentEncoding.GetBytes(currentItem.MetaList.ToJSON(options.PrettyPrint)); context.Send(cacheResponse, true); } //context.SendJSON(GetMetaList(context, (PxMenuItem)currentItem).ToJSON(options.PrettyPrint)); } else if (context.Request.HttpMethod == "GET" && currentItem.MenuType == typeof(TableLink)) { // Current item is not a list. Try to return table meta data cacheResponse.ContentType = "application/json; charset=" + System.Text.Encoding.UTF8.WebName; cacheResponse.ResponseData = context.Response.ContentEncoding.GetBytes(GetTableMeta(context, language, db, strNodePath).ToJSON(options.PrettyPrint)); context.Send(cacheResponse, true); // Logs usage _usageLogger.Info(String.Format("url={0}, type=metadata, caller={1}, cached=false, format=json", context.Request.RawUrl, context.Request.UserHostAddress)); } else if (context.Request.HttpMethod == "POST" && currentItem.MenuType == typeof(TableLink)) { // Current item is not a list. Try to return table data. SendTableData(context, language, db, routeParts.ToArray(), cacheResponse); } else { context.SendJSONError(Error("Parameter error", false), 400); // Logs usage _usageLogger.Info(String.Format("url={0}, type=error, caller={1}, cached=false, message=Parameter error", context.Request.RawUrl, context.Request.UserHostAddress)); } //} } } #if DEBUG stopWatch.Stop(); logTime.InfoFormat(System.Reflection.MethodBase.GetCurrentMethod().Name + " Done in ms = {0}", stopWatch.ElapsedMilliseconds); #endif } catch (HttpException ex) { #if DEBUG stopWatch.Stop(); logTime.InfoFormat(System.Reflection.MethodBase.GetCurrentMethod().Name + " Error Done in ms = {0}", stopWatch.ElapsedMilliseconds); #endif if (ex.GetHttpCode() == 429) { context.Response.TrySkipIisCustomErrors = true; context.SendJSONError(Error(ex.Message, false), 429); } else { context.SendJSONError(Error("Parameter error", false), ex.GetHttpCode()); } // Logs usage _usageLogger.Info(String.Format("url={0}, type=error, caller={1}, cached=false", context.Request.RawUrl, context.Request.UserHostAddress), ex); _logger.Warn(ex); } catch (Exception ex) { //if (context.Request.HttpMethod != "OPTIONS") //{ #if DEBUG stopWatch.Stop(); logTime.InfoFormat(System.Reflection.MethodBase.GetCurrentMethod().Name + " Error Done in ms = {0}", stopWatch.ElapsedMilliseconds); #endif try { context.SendJSONError(Error("Parameter error", false), 400); } catch (Exception sendEx) { _logger.Error(String.Format("url={0}, type=error, caller={1}", context.Request.RawUrl, context.Request.UserHostAddress), sendEx); } _usageLogger.Info(String.Format("url={0}, type=error, caller={1}, cached=false ", context.Request.RawUrl, context.Request.UserHostAddress), ex); _logger.Warn(ex); //} } }