See More

namespace AustinHarris.JsonRpc { using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; using Newtonsoft.Json; using System.Threading.Tasks; using System.Collections.Concurrent; using Newtonsoft.Json.Linq; public class Handler { #region Members //private static Handler current; private static ConcurrentDictionary _sessionHandlers; private static string _defaultSessionId; #endregion #region Constructors static Handler() { //current = new Handler(Guid.NewGuid().ToString()); _defaultSessionId = Guid.NewGuid().ToString(); _sessionHandlers = new ConcurrentDictionary(); _sessionHandlers[_defaultSessionId]= new Handler(_defaultSessionId); } private Handler(string sessionId) { SessionId = sessionId; this.MetaData = new SMD(); this.Handlers = new Dictionary(); } #endregion #region Properties ///

/// Returns the SessionID of the default session /// /// public static string DefaultSessionId() { return _defaultSessionId; } /// /// Gets a specific session /// /// The sessionId of the handler you want to retrieve. /// public static Handler GetSessionHandler(string sessionId) { return _sessionHandlers.GetOrAdd(sessionId, new Handler(sessionId)); } /// /// gets the default session /// /// The default Session Handler public static Handler GetSessionHandler() { return GetSessionHandler(_defaultSessionId); } /// /// Removes and clears the Handler with the specific sessionID from the registry of Handlers /// /// public static void DestroySession(string sessionId) { Handler h; _sessionHandlers.TryRemove(sessionId,out h); h.Handlers.Clear(); h.MetaData.Services.Clear(); } /// /// Removes and clears the current Handler from the registry of Handlers /// public void Destroy() { DestroySession(SessionId); } /// /// Gets the default session handler /// public static Handler DefaultHandler { get { return GetSessionHandler(_defaultSessionId); } } /// /// The sessionID of this Handler /// public string SessionId { get; private set; } private static ConcurrentDictionary RpcContexts = new ConcurrentDictionary(); private static ConcurrentDictionary RpcExceptions = new ConcurrentDictionary(); /// /// Provides access to a context specific to each JsonRpc method invocation. /// Warning: Must be called from within the execution context of the jsonRpc Method to return the context /// /// public static object RpcContext() { if (Task.CurrentId == null) return null; if (RpcContexts.ContainsKey(Task.CurrentId.Value) == false) return null; return RpcContexts[Task.CurrentId.Value]; } /// /// Allows you to set the exception used in in the JsonRpc response. /// Warning: Must be called from within the execution context of the jsonRpc method. /// /// public static void RpcSetException(JsonRpcException exception) { if (Task.CurrentId != null) RpcExceptions[Task.CurrentId.Value] = exception; else throw new InvalidOperationException("This method is only valid when used within the context of a method marked as a JsonRpcMethod, and that method must of been invoked by the JsonRpc Handler."); } private void RemoveRpcException() { if (Task.CurrentId != null) { var id = Task.CurrentId.Value; RpcExceptions[id] = null; JsonRpcException va; RpcExceptions.TryRemove(id, out va); } } private AustinHarris.JsonRpc.PreProcessHandler externalPreProcessingHandler; private Func externalErrorHandler; private Func parseErrorHandler; private Dictionary Handlers { get; set; } #endregion /// /// This metadata contains all the types and mappings of all the methods in this handler. Warning: Modifying this directly could cause your handler to no longer function. /// public SMD MetaData { get; set; } #region Public Methods /// /// Registers a jsonRpc method name (key) to be mapped to a specific function /// /// The Method as it will be called from JsonRpc /// The method that will be invoked /// public bool Register(string key, Delegate handle) { var result = false; if (!this.Handlers.ContainsKey(key)) { this.Handlers.Add(key, handle); } return result; } public void UnRegister(string key) { this.Handlers.Remove(key); MetaData.Services.Remove(key); } /// /// Invokes a method to handle a JsonRpc request. /// /// JsonRpc Request to be processed /// Optional context that will be available from within the jsonRpcMethod. /// public JsonResponse Handle(JsonRequest Rpc, Object RpcContext = null) { AddRpcContext(RpcContext); var preProcessingException = PreProcess(Rpc, RpcContext); if (preProcessingException != null) { return new JsonResponse() { Error = preProcessingException, Id = Rpc.Id }; } SMDService metadata = null; Delegate handle = null; var haveDelegate = this.Handlers.TryGetValue(Rpc.Method, out handle); var haveMetadata = this.MetaData.Services.TryGetValue(Rpc.Method, out metadata); if (haveDelegate == false || haveMetadata == false || metadata == null || handle == null) { return new JsonResponse() { Result = null, Error = new JsonRpcException(-32601, "Method not found", "The method does not exist / is not available."), Id = Rpc.Id }; } if (Rpc.Params is ICollection == false) { return new JsonResponse() { Result = null, Error = new JsonRpcException(-32602, "Invalid params", "The number of parameters could not be counted"), Id = Rpc.Id }; } bool isJObject = Rpc.Params is Newtonsoft.Json.Linq.JObject; bool isJArray = Rpc.Params is Newtonsoft.Json.Linq.JArray; object[] parameters = null; bool expectsRefException = false; var metaDataParamCount = metadata.parameters.Count(x => x != null); var getCount = Rpc.Params as ICollection; var loopCt = getCount.Count; var paramCount = loopCt; if (paramCount == metaDataParamCount - 1 && metadata.parameters[metaDataParamCount-1].ObjectType.Name.Contains(typeof(JsonRpcException).Name)) { paramCount++; expectsRefException = true; } parameters = new object[paramCount]; if (isJArray) { var jarr = ((Newtonsoft.Json.Linq.JArray)Rpc.Params); //var loopCt = jarr.Count; //var pCount = loopCt; //if (pCount == metaDataParamCount - 1 && metadata.parameters[metaDataParamCount].GetType() == typeof(JsonRpcException)) // pCount++; //parameters = new object[pCount]; for (int i = 0; i < loopCt; i++) { parameters[i] = CleanUpParameter(jarr[i], metadata.parameters[i]); } } else if (isJObject) { var jo = Rpc.Params as Newtonsoft.Json.Linq.JObject; //var loopCt = jo.Count; //var pCount = loopCt; //if (pCount == metaDataParamCount - 1 && metadata.parameters[metaDataParamCount].GetType() == typeof(JsonRpcException)) // pCount++; //parameters = new object[pCount]; var asDict = jo as IDictionary; for (int i = 0; i < loopCt; i++) { if (asDict.ContainsKey(metadata.parameters[i].Name) == false) { return new JsonResponse() { Error = ProcessException(Rpc, new JsonRpcException(-32602, "Invalid params", string.Format("Named parameter '{0}' was not present.", metadata.parameters[i].Name) )) ,Id = Rpc.Id }; } parameters[i] = CleanUpParameter(jo[metadata.parameters[i].Name], metadata.parameters[i]); } } // Optional Parameter support // check if we still miss parameters compared to metadata which may include optional parameters. // if the rpc-call didn't supply a value for an optional parameter, we should be assinging the default value of it. if (parameters.Length < metaDataParamCount && metadata.defaultValues.Length > 0) // rpc call didn't set values for all optional parameters, so we need to assign the default values for them. { var paramIndex = parameters.Length; // the index we should start storing default values of optional parameters. var missingParamsCount = metaDataParamCount - parameters.Length; // the amount of optional parameters without a value set by rpc-call. Array.Resize(ref parameters, parameters.Length + metadata.defaultValues.Length); // resize the array to include all optional parameters. // we need to add in reverse order as parameters can appear after all required parameters. // as some of the optional parameters could already have assigned their values in rpc-call, // by starting from the end we can make sure we only add the required default values. for (int k = missingParamsCount; k > 0; k--) { var optionalParamIndex = k - 1; // the index of the optional parameter we will be currently setting a default value. parameters[paramIndex] = metadata.defaultValues[optionalParamIndex].Value; // set the default value for the optional parameter that rpc-call didn't set a value for. paramIndex++; paramCount++; // we need to increase the paramCount by one each time we add default-value for an optional parameter that rpc-call didn't set a value for. } } if (parameters.Length != metaDataParamCount) { return new JsonResponse() { Error = ProcessException(Rpc, new JsonRpcException(-32602, "Invalid params", string.Format("Expecting {0} parameters, and received {1}", metadata.parameters.Length, parameters.Length) )), Id = Rpc.Id }; } try { var results = handle.DynamicInvoke(parameters); var last = parameters.Length>0 ? parameters[paramCount - 1]:null; JsonRpcException contextException; if (Task.CurrentId.HasValue && RpcExceptions.TryRemove(Task.CurrentId.Value, out contextException)) { return new JsonResponse() { Error = ProcessException(Rpc, contextException), Id = Rpc.Id }; } if (expectsRefException && last != null && last is JsonRpcException) { return new JsonResponse() { Error = ProcessException(Rpc, last as JsonRpcException), Id = Rpc.Id }; } return new JsonResponse() { Result = results }; } catch (Exception ex) { if (ex is TargetParameterCountException) { return new JsonResponse() { Error = ProcessException(Rpc, new JsonRpcException(-32602, "Invalid params", ex)) }; } // We really dont care about the TargetInvocationException, just pass on the inner exception if (ex is JsonRpcException) { return new JsonResponse() { Error = ProcessException(Rpc, ex as JsonRpcException) }; } if (ex.InnerException != null && ex.InnerException is JsonRpcException) { return new JsonResponse() { Error = ProcessException(Rpc, ex.InnerException as JsonRpcException) }; } else if (ex.InnerException != null) { return new JsonResponse() { Error = ProcessException(Rpc, new JsonRpcException(-32603, "Internal Error", ex.InnerException)) }; } return new JsonResponse() { Error = ProcessException(Rpc, new JsonRpcException(-32603, "Internal Error", ex)) }; } finally { RemoveRpcContext(); } } private void AddRpcContext(object RpcContext) { if (Task.CurrentId != null) RpcContexts[Task.CurrentId.Value] = RpcContext; } private void RemoveRpcContext() { if (Task.CurrentId != null) { var id = Task.CurrentId.Value; RpcContexts[id] = null; object va; RpcContexts.TryRemove(id, out va); } } private JsonRpcException ProcessException(JsonRequest req,JsonRpcException ex) { if(externalErrorHandler!=null) return externalErrorHandler(req,ex); return ex; } internal JsonRpcException ProcessParseException(string req,JsonRpcException ex) { if (parseErrorHandler != null) return parseErrorHandler(req, ex); return ex; } internal void SetErrorHandler(Func handler) { externalErrorHandler = handler; } internal void SetParseErrorHandler(Func handler) { parseErrorHandler = handler; } #endregion private object CleanUpParameter(object p, SMDAdditionalParameters metaData) { var bob = p as JValue; //if (bob != null && (bob.Value == null || bob.Value.GetType() == metaData.ObjectType)) if (bob != null && (bob.Value == null || metaData.ObjectType.IsAssignableFrom(bob.Value.GetType()))) { return bob.Value; } var paramI = p; try { return Newtonsoft.Json.JsonConvert.DeserializeObject(paramI.ToString(), metaData.ObjectType); } catch (Exception ex) { // no need to throw here, they will // get an invalid cast exception right after this. } return paramI; } private JsonRpcException PreProcess(JsonRequest request, object context) { if (externalPreProcessingHandler == null) return null; return externalPreProcessingHandler(request, context); } internal void SetPreProcessHandler(AustinHarris.JsonRpc.PreProcessHandler handler) { externalPreProcessingHandler = handler; } } }