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);
}
}
}