Click here to Skip to main content
15,885,366 members
Articles / Web Development

RaptorDB REST

Rate me:
Please Sign up or sign in to vote.
4.92/5 (11 votes)
5 Dec 2013CPOL10 min read 36K   1K   29  
A REST web interface for RaptorDB the document database
using System;
using RaptorDB.Common;
using System.Net;
using System.Text;
using System.IO;
using System.Threading.Tasks;
using RaptorDB;
using System.Reflection;
using System.CodeDom.Compiler;
using System.Text.RegularExpressions;
using System.ComponentModel;
using System.Collections.Generic;
using System.IO.Compression;

namespace RaptorDBRest
{
    #region [    rdb rest helper classes    ]
    public class RDBRoute
    {
        public string URL;
        public Type EntityType;
        public string Viewname;
        public ServerSideFunc function;
    }

    public interface IRouteAPI
    {
        void AddRoute(RDBRoute route);
        void RegisterView<T>(View<T> view);
    }

    public interface IRDBRouting
    {
        void Initialize(IRouteAPI api);
    }

    public class RDBJsonContainer
    {
        public string URL;
        public DateTime date;
        public string json;
        public string useragent;
        public Guid docid;
    }
    #endregion


    public class RestServer : IRouteAPI
    {
        public RestServer(int HttpPort, string path)
        {

            _port = HttpPort;
            _rdb = RaptorDB.RaptorDB.Open(path);
            save = _rdb.GetType().GetMethod("Save", BindingFlags.Instance | BindingFlags.Public);
            register = _rdb.GetType().GetMethod("RegisterView", BindingFlags.Instance | BindingFlags.Public);
            _path = path;
            Task.Factory.StartNew(() => Start(), TaskCreationOptions.LongRunning);
        }
        private string _S = Path.DirectorySeparatorChar.ToString();
        private ILog _log = LogManager.GetLogger(typeof(RestServer));
        private bool _run = true;
        private RaptorDB.RaptorDB _rdb;
        private int _port;
        private string _path;
        private SafeDictionary<string, RDBRoute> _routing = new SafeDictionary<string, RDBRoute>();
        private HttpListener _server;
        private KeyStore<Guid> _jsonstore;


        public void Stop()
        {
            _run = false;
            _jsonstore.Shutdown();
            _rdb.Shutdown();
        }

        public void AddRoute(RDBRoute route)
        {
            _routing.Add(route.URL, route);
        }

        public void RegisterView<T>(View<T> view)
        {
            _rdb.RegisterView(view);
        }


        public IRaptorDB RDB { get { return _rdb; } }

        #region [   private   ]

        private void Start()
        {
            // compile _path\Routing\*.route files and register
            Directory.CreateDirectory(_path + _S + "Routing");
            // delete existing compiled files
            foreach (var f in Directory.GetFiles(_path + _S + "Routing", "*.dll"))
                File.Delete(f);
            // compile route files
            CompileAndRegisterScriptRoutes(_path + _S + "Routing");

            _jsonstore = new KeyStore<Guid>(_path + _S + "Datajson" + _S + "json.mgdat", true);

            _server = new HttpListener();
            //_server.AuthenticationSchemes = AuthenticationSchemes.Basic;
            _server.Prefixes.Add("http://*:" + _port + "/");
            _server.Start();
            while (_run)
            {
                var context = _server.BeginGetContext(new AsyncCallback(ListenerCallback), _server);
                context.AsyncWaitHandle.WaitOne();
            }
        }

        private MethodInfo register = null;
        private void CompileAndRegisterScriptRoutes(string routefolder)
        {
            // compile & register views
            string[] files = Directory.GetFiles(routefolder, "*.route");

            foreach (var fn in files)
            {
                Assembly a = CompileScript(fn);
                if (a != null)
                {
                    foreach (var t in a.GetTypes())
                    {
                        if (typeof(IRDBRouting).IsAssignableFrom(t))
                        {
                            IRDBRouting r = (IRDBRouting)Activator.CreateInstance(t);
                            r.Initialize(this);
                        }
                        // load views if exists
                        foreach (var att in t.GetCustomAttributes(typeof(RegisterViewAttribute), false))
                        {
                            try
                            {
                                object o = Activator.CreateInstance(t);
                                //  handle types when view<T> also
                                Type[] args = t.GetGenericArguments();
                                if (args.Length == 0)
                                    args = t.BaseType.GetGenericArguments();
                                Type tt = args[0];
                                var m = register.MakeGenericMethod(new Type[] { tt });
                                m.Invoke(_rdb, new object[] { o });
                            }
                            catch (Exception ex)
                            {
                                _log.Error(ex);
                            }
                        }
                    }
                }
            }
        }

        private Assembly CompileScript(string file)
        {
            try
            {
                _log.Debug("Compiling route script : " + file);
                CodeDomProvider compiler = CodeDomProvider.CreateProvider("CSharp");

                CompilerParameters compilerparams = new CompilerParameters();
                compilerparams.GenerateInMemory = false;
                compilerparams.GenerateExecutable = false;
                compilerparams.OutputAssembly = file.Replace(".route", ".dll");
                compilerparams.CompilerOptions = "/optimize";

                Regex regex = new Regex(
                    @"\/\/\s*ref\s*\:\s*(?<refs>.*)",
                    System.Text.RegularExpressions.RegexOptions.IgnoreCase);

                compilerparams.ReferencedAssemblies.Add(typeof(View<>).Assembly.Location); //raprotdb.common.dll
                compilerparams.ReferencedAssemblies.Add(typeof(object).Assembly.Location); //mscorlib.dll
                compilerparams.ReferencedAssemblies.Add(typeof(System.Uri).Assembly.Location); //system.dll
                compilerparams.ReferencedAssemblies.Add(typeof(System.Linq.Enumerable).Assembly.Location);//system.core.dll
                compilerparams.ReferencedAssemblies.Add(typeof(IRDBRouting).Assembly.Location); //raptordb.rest.dll

                foreach (Match m in regex.Matches(File.ReadAllText(file)))
                {
                    string str = m.Groups["refs"].Value.Trim();
                    Assembly a = Assembly.LoadWithPartialName(Path.GetFileNameWithoutExtension(str));//load from GAC if possible
                    if (a != null)
                        compilerparams.ReferencedAssemblies.Add(a.Location);
                    else
                    {
                        string assm = Path.GetDirectoryName(this.GetType().Assembly.Location) + _S + str;
                        a = Assembly.LoadFrom(assm);
                        if (a != null)
                            compilerparams.ReferencedAssemblies.Add(a.Location);
                        else
                            _log.Error("unable to find referenced file for view compiling : " + str);
                    }
                }

                CompilerResults results = compiler.CompileAssemblyFromFile(compilerparams, file);

                if (results.Errors.HasErrors == true)
                {
                    _log.Error("Error compiling route definition : " + file);
                    foreach (var e in results.Errors)
                        _log.Error(e.ToString());
                    return null;
                }

                return results.CompiledAssembly;
            }
            catch (Exception ex)
            {
                _log.Error("Error compiling route definition : " + file);
                _log.Error(ex);
                return null;
            }
        }

        private void ListenerCallback(IAsyncResult ar)
        {
            var listener = ar.AsyncState as HttpListener;

            var ctx = listener.EndGetContext(ar);

            //do some stuff
            string path = ctx.Request.Url.GetComponents(UriComponents.Path, UriFormat.Unescaped).ToLower();
            RDBRoute r = null;
            ctx.Response.ContentEncoding = UTF8Encoding.UTF8;
            if (_routing.TryGetValue(path, out r))
            {
                string mth = ctx.Request.HttpMethod;
                if (mth == "POST" || mth == "PUT")
                    ProcessPOST(_rdb, ctx, path, r);
                else
                    ProcessGET(_rdb, ctx, path, r);
            }
            else
                WriteResponse(ctx, 404, "route path not found : " + ctx.Request.Url.GetComponents(UriComponents.Path, UriFormat.Unescaped));

            ctx.Response.OutputStream.Close();
        }

        private void ProcessPOST(IRaptorDB rdb, HttpListenerContext ctx, string path, RDBRoute route)
        {
            try
            {
                Guid docid = Guid.Empty;
                string qry = ctx.Request.Url.GetComponents(UriComponents.Query, UriFormat.Unescaped);
                if (qry == "")
                {
                    _log.Error("POST-ing requires a GUID in route : " + route.URL);
                    WriteResponse(ctx, 404, "POST-ing requires a GUID in route : " + route.URL);
                    return;
                }
                else // parse out guid from url 
                {
                    string[] s = qry.Split('=');
                    docid = Guid.Parse(s[1].Trim());
                }

                if (route.EntityType == null)
                {
                    _log.Error("POST-ing to an undefined entity in route : " + route.URL);
                    WriteResponse(ctx, 404, "POST-ing to an undefined entity in route : " + route.URL);
                    return;
                }

                using (var reader = new StreamReader(ctx.Request.InputStream, ctx.Request.ContentEncoding))
                {
                    string text = reader.ReadToEnd();

                    // create a container
                    RDBJsonContainer c = new RDBJsonContainer();
                    c.date = FastDateTime.Now;
                    c.json = text;
                    c.URL = path;
                    c.docid = docid;
                    // TODO : save useragent info

                    fastJSON.JSON.Instance.Parameters.IgnoreCaseOnDeserialize = true;
                    // convert json to object   
                    object obj = fastJSON.JSON.Instance.ToObject(text, route.EntityType);

                    // save object to RDB and handle transaction
                    var m = GetSave(obj.GetType());
                    bool ret = (bool)m.Invoke(_rdb, new object[] { docid, obj });
                    if (ret)
                    {
                        // save json container to keystore
                        _jsonstore.SetObject(docid, c);
                        WriteResponse(ctx, 200, "posted : " + docid);
                    }
                    else
                        WriteResponse(ctx, 500, "unable to save : " + text);
                }
            }
            catch (Exception ex)
            {
                _log.Error(ex);
                WriteResponse(ctx, 500, "" + ex);
            }
        }

        private void WriteResponse(HttpListenerContext ctx, int code, string msg)
        {
            ctx.Response.StatusCode = code;
            byte[] b = Encoding.UTF8.GetBytes(msg);
            ctx.Response.OutputStream.Write(b, 0, b.Length);
        }

        private MethodInfo save = null;
        private SafeDictionary<Type, MethodInfo> _savecache = new SafeDictionary<Type, MethodInfo>();
        private MethodInfo GetSave(Type type)
        {
            MethodInfo m = null;
            if (_savecache.TryGetValue(type, out m))
                return m;

            m = save.MakeGenericMethod(new Type[] { type });
            _savecache.Add(type, m);
            return m;
        }

        private Regex _start_regex = new Regex(@"\??\s*start\s*\=\s*[-+]?(?<start>\d*)", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled);
        private Regex _count_regex = new Regex(@"\??\s*count\s*\=\s*[-+]?(?<count>\d*)", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled);
        private void ProcessGET(IRaptorDB rdb, HttpListenerContext ctx, string path, RDBRoute route)
        {
            try
            {
                string qry = ctx.Request.Url.GetComponents(UriComponents.Query, UriFormat.Unescaped);
                if (route.EntityType != null)
                {
                    if (qry != "")
                    {
                        // fetch the json document
                        string[] s = qry.Split('=');
                        object obj;
                        if (_jsonstore.GetObject(Guid.Parse(s[1].Replace("\"", "")), out obj))
                        {
                            RDBJsonContainer d = (RDBJsonContainer)obj;
                            WriteResponse(ctx, 200, d.json);
                            return;
                        }
                    }

                    WriteResponse(ctx, 404, "GUID not found :" + qry);
                    return;
                }

                if (route.Viewname == null && route.function != null)
                {
                    var o = route.function(_rdb, qry);
                    Result<object> resf = new Result<object>(true);
                    resf.Rows = o;
                    resf.TotalCount = o.Count;
                    resf.Count = o.Count;
                    
                    var s = fastJSON.JSON.Instance.ToJSON(resf, new fastJSON.JSONParameters { UseExtensions = false, UseFastGuid = false, EnableAnonymousTypes = true });
                    ctx.Response.ContentType = "application/json";
                    WriteResponse(ctx, 200, s);
                    return;
                }

                // parse "start" and "count" from qry if exists
                int start = 0;
                int count = -1;

                var m = _start_regex.Match(qry);
                if (m.Success)
                {
                    start = int.Parse(m.Groups["start"].Value);
                    qry = qry.Replace(m.Value, "");
                }
                m = _count_regex.Match(qry);
                if (m.Success)
                {
                    count = int.Parse(m.Groups["count"].Value);
                    qry = qry.Replace(m.Value, "");
                }

                var res = rdb.Query(route.Viewname, qry, start, count);
                var str = fastJSON.JSON.Instance.ToJSON(res, new fastJSON.JSONParameters { UseExtensions = false, UseFastGuid = false });
                ctx.Response.ContentType = "application/json";
                WriteResponse(ctx, 200, str);
            }
            catch (Exception ex)
            {
                WriteResponse(ctx, 500, "" + ex);
            }
        }
        #endregion

        void IRouteAPI.AddRoute(RDBRoute route)
        {
            _routing.Add(route.URL, route);
        }

        void IRouteAPI.RegisterView<T>(View<T> view)
        {
            _rdb.RegisterView(view);
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect -
United Kingdom United Kingdom
Mehdi first started programming when he was 8 on BBC+128k machine in 6512 processor language, after various hardware and software changes he eventually came across .net and c# which he has been using since v1.0.
He is formally educated as a system analyst Industrial engineer, but his programming passion continues.

* Mehdi is the 5th person to get 6 out of 7 Platinum's on Code-Project (13th Jan'12)
* Mehdi is the 3rd person to get 7 out of 7 Platinum's on Code-Project (26th Aug'16)

Comments and Discussions