Пример #1
0
        /// <summary>
        /// Stores a object in the cache
        /// </summary>
        /// <param name="data"></param>
        public void Store(ResponseBucket data, TimeSpan?time = null)
        {
            if (time == null)
            {
                time = new TimeSpan(0, 2, 0);
            }

            //Check if caching is enabled
            if (!Settings.Current.EnableCache)
            {
                return;
            }

            if (System.Web.HttpRuntime.Cache[data.Key] == null && data.CreationTime > _cachedStartTime)
            {
                if (_apiCacheLogger.IsDebugEnabled)
                {
                    _apiCacheLogger.DebugFormat("Adding key={0} to Cache", data.Key);
                }

                lock (CacheLock)
                {
                    if (System.Web.HttpRuntime.Cache[data.Key] == null)
                    {
                        System.Web.HttpRuntime.Cache.Add(data.Key, data, null, System.Web.Caching.Cache.NoAbsoluteExpiration, time.Value, System.Web.Caching.CacheItemPriority.Normal, null);
                    }
                }
            }
        }
Пример #2
0
        /// <summary>
        /// Sends data
        /// </summary>
        /// <param name="context"></param>
        /// <param name="cacheResponse"></param>
        /// <param name="doCache"></param>
        public static void Send(this HttpContext context, ResponseBucket cacheResponse, bool doCache)
        {
            context.Send(cacheResponse.ContentType, cacheResponse.ResponseData);

            // Store request in cache
            if (doCache)
            {
                ApiCache.Current.Store(cacheResponse);
            }
        }
Пример #3
0
        /// <summary>
        /// Gets the menu object for the specified database
        /// </summary>
        /// <remarks></remarks>
        /// <returns>A menu object</returns>
        public static MenuObject GetMenu(string db, string language, string[] nodePath)
        {
            if (ExposedDatabases.DatabaseConfigurations[language].ContainsKey(db) == false)
            {
                return(null);
            }

            var        database = ExposedDatabases.DatabaseConfigurations[language][db];
            var        dbtype   = database.Type;
            string     cacheKey = ApiCache.CreateKey($"{db}_{language}_{string.Join("_", nodePath)}");
            MenuObject menuObj  = null;

            ResponseBucket cacheResponse = ApiCache.Current.Fetch(cacheKey);

            if (cacheResponse != null)
            {
                return(cacheResponse.Menu);
            }

            try
            {
                if (dbtype == "PX")
                {
                    menuObj = MenuObject.Create(GetPxMenu(db, language, string.Join("\\", nodePath)));
                }
                else if (dbtype == "CNMM")
                {
                    string tid  = nodePath.Last();
                    string menu = nodePath.Length > 1 ? nodePath[nodePath.Length - 2]:"START";

                    if (tid.Contains("'") || menu.Contains("'"))
                    {
                        throw new ArgumentException("Possible SQL injection");
                    }
                    menuObj = MenuObject.Create(GetCnmmMenu(db, language, tid, menu));
                }

                // Request object to be stored in cache
                cacheResponse      = new ResponseBucket();
                cacheResponse.Key  = cacheKey;
                cacheResponse.Menu = menuObj;
                ApiCache.Current.Store(cacheResponse, new TimeSpan(24, 0, 0));

                return(menuObj);
            }
            catch (Exception e)
            {
                _logger.Error("Error retrieving menu", e);
                return(null);
            }
        }
Пример #4
0
        /// <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);
                //}
            }
        }
Пример #5
0
        /// <summary>
        /// Serializes and sends table data back to the client
        /// </summary>
        /// <param name="context"></param>
        private void SendTableData(HttpContext context, string language, string db, string[] tablePath, ResponseBucket cacheResponse)
        {
            //// TODO: Limit data size?
            //string data = "";
            //using (var stream = new StreamReader(context.Request.InputStream))
            //{
            //    data = stream.ReadToEnd();
            //}

            var tableQuery = JsonHelper.Deserialize <TableQuery>(cacheResponse.PostData) as TableQuery;

            if (tableQuery.Response == null || tableQuery.Response.Format == null)
            {
                tableQuery.Response = new QueryResponse()
                {
                    Format = _defaultResponseFormat
                };
            }

            // Initialize the builder
            PCAxis.Paxiom.IPXModelBuilder builder = GetPXBuilder(language, db, tablePath);
            builder.DoNotApplyCurrentValueSet = true;  // DoNotApplyCurrentValueSet means the "client that made the request" is an api(, not a GUI) so that
                                                       // CNMM2.4 property DefaultInGUI (for Valueset/grouping) should not be used
            builder.SetPreferredLanguage(language);
            builder.BuildForSelection();

            // Process selections
            List <PCAxis.Paxiom.Selection> selections;

            try
            {
                selections = PCAxisRepository.BuildSelections(builder, tableQuery);
            }

            catch (Exception ex)
            {
                var message = ex is HttpException ? ex.Message : "Parameter error";

                cacheResponse.ResponseData = context.Response.ContentEncoding.GetBytes(Error(message, false));
                context.SendJSONError(cacheResponse, 400, true);
                // Logs usage
                _usageLogger.Info(String.Format("url={0}, type=error, caller={1}, cached=false, message=Parameter error", context.Request.RawUrl, context.Request.UserHostAddress));
                return;
            }

            // Check that the number of selected cells do not exceed the limit
            long cellCount = 1;

            foreach (var sel in selections)
            {
                if (sel.ValueCodes.Count > 0)
                {
                    cellCount *= sel.ValueCodes.Count;
                }
            }
            if (cellCount > Settings.Current.FetchCellLimit)
            {
                Write403Response(context);
                return;
            }

            builder.BuildForPresentation(selections.ToArray());

            // Output handling starts here
            context.Response.Clear();

            IWebSerializer serializer = WebSerializerSwitch.GetSerializer(tableQuery.Response.Format);

            //serializer.Serialize(builder.Model, context.Response);
            serializer.Serialize(builder.Model, cacheResponse);
            //context.Response.AddHeader("Content-Type", cacheResponse.ContentType);
            //context.Response.OutputStream.Write(cacheResponse.ResponseData, 0, cacheResponse.ResponseData.Length);
            context.Send(cacheResponse, true);
            //Logs usage
            _usageLogger.Info(String.Format("url={0}, type=data, caller={3}, cached=false, format={1}, matrix-size={2}", context.Request.RawUrl, tableQuery.Response.Format, builder.Model.Data.MatrixSize, context.Request.UserHostAddress));
        }
Пример #6
0
        /// <summary>
        /// Serializes and sends table data back to the client
        /// </summary>
        /// <param name="context"></param>
        private void SendTableData(HttpContext context, string language, string db, string[] tablePath, ResponseBucket cacheResponse)
        {
            //// TODO: Limit data size?
            //string data = "";
            //using (var stream = new StreamReader(context.Request.InputStream))
            //{
            //    data = stream.ReadToEnd();
            //}

            var tableQuery = JsonHelper.Deserialize <TableQuery>(cacheResponse.PostData) as TableQuery;

            if (tableQuery.Response == null || tableQuery.Response.Format == null)
            {
                tableQuery.Response = new QueryResponse()
                {
                    Format = _defaultResponseFormat
                };
            }

            // Initialize the builder
            PCAxis.Paxiom.IPXModelBuilder builder = GetPXBuilder(language, db, tablePath);
            builder.DoNotApplyCurrentValueSet = true;  // DoNotApplyCurrentValueSet means the "client that made the request" is an api(, not a GUI) so that
                                                       // CNMM2.4 property DefaultInGUI (for Valueset/grouping) should not be used
            builder.SetPreferredLanguage(language);
            builder.BuildForSelection();

            // Process selections
            var selections = PCAxisRepository.BuildSelections(builder, tableQuery);

            // Check that the number of selected cells do not exceed the limit
            long cellCount = 1;

            foreach (var sel in selections)
            {
                if (sel.ValueCodes.Count > 0)
                {
                    cellCount *= sel.ValueCodes.Count;
                }
            }
            if (cellCount > Settings.Current.FetchCellLimit)
            {
                Write403Response(context);
                return;
            }

            builder.BuildForPresentation(selections.ToArray());

            // Output handling starts here
            context.Response.Clear();

            IWebSerializer serializer;

            switch (tableQuery.Response.Format != null ? tableQuery.Response.Format.ToLower() : null)
            {
            case null:
            case "px":
                serializer = new PxSerializer();
                break;

            case "csv":
                serializer = new CsvSerializer();
                break;

            case "json":
                serializer = new JsonSerializer();
                break;

            case "json-stat":
                serializer = new JsonStatSeriaizer();
                break;

            case "json-stat2":
                serializer = new JsonStat2Seriaizer();
                break;

            case "xlsx":
                serializer = new XlsxSerializer();
                break;

            //case "png":
            //    int? width = tableQuery.Response.GetParamInt("width");
            //    int? height = tableQuery.Response.GetParamInt("height");
            //    string encoding = tableQuery.Response.GetParamString("encoding");
            //    serializer = new ChartSerializer(width, height, encoding);
            //    break;
            case "sdmx":
                serializer = new SdmxDataSerializer();
                break;

            default:
                throw new NotImplementedException("Serialization for " + tableQuery.Response.Format + " is not implemented");
            }
            //serializer.Serialize(builder.Model, context.Response);
            serializer.Serialize(builder.Model, cacheResponse);
            //context.Response.AddHeader("Content-Type", cacheResponse.ContentType);
            //context.Response.OutputStream.Write(cacheResponse.ResponseData, 0, cacheResponse.ResponseData.Length);
            context.Send(cacheResponse, true);
            //Logs usage
            _usageLogger.Info(String.Format("url={0}, type=data, caller={3}, cached=false, format={1}, matrix-size={2}", context.Request.RawUrl, tableQuery.Response.Format, builder.Model.Data.MatrixSize, context.Request.UserHostAddress));
        }
Пример #7
0
 public static void SendJSONError(this HttpContext context, ResponseBucket data, int code, bool doCache)
 {
     data.HttpResponseCode = code;
     data.ContentType      = "application/json";
     Send(context, data, doCache);
 }