parent
aa35e4b6a6
commit
6bd8ee846c
73 changed files with 3863 additions and 27 deletions
Binary file not shown.
Binary file not shown.
@ -0,0 +1,12 @@ |
||||
using Microsoft.AspNetCore.Builder; |
||||
|
||||
namespace ASPNetCoreExecutor |
||||
{ |
||||
public static class ApplicationBuilderExtensions |
||||
{ |
||||
public static IApplicationBuilder UseXxlJobExecutor(this IApplicationBuilder @this) |
||||
{ |
||||
return @this.UseMiddleware<XxlJobExecutorMiddleware>(); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,41 @@ |
||||
using System; |
||||
using System.Net; |
||||
using System.Threading.Tasks; |
||||
using DotXxlJob.Core; |
||||
using Microsoft.AspNetCore.Http; |
||||
using Microsoft.Extensions.DependencyInjection; |
||||
|
||||
namespace ASPNetCoreExecutor |
||||
{ |
||||
public class XxlJobExecutorMiddleware |
||||
{ |
||||
private readonly IServiceProvider _provider; |
||||
private readonly RequestDelegate _next; |
||||
|
||||
private readonly XxlRpcServiceHandler _rpcService; |
||||
public XxlJobExecutorMiddleware(IServiceProvider provider, RequestDelegate next) |
||||
{ |
||||
_provider = provider; |
||||
_next = next; |
||||
|
||||
_rpcService = _provider.GetRequiredService<XxlRpcServiceHandler>(); |
||||
} |
||||
|
||||
|
||||
public async Task Invoke(HttpContext context) |
||||
{ |
||||
|
||||
if ("POST".Equals(context.Request.Method, StringComparison.OrdinalIgnoreCase) && |
||||
"application/octet-stream".Equals(context.Request.ContentType, StringComparison.OrdinalIgnoreCase)) |
||||
{ |
||||
var rsp = await _rpcService.HandlerAsync(context.Request.Body); |
||||
|
||||
context.Response.StatusCode = (int) HttpStatusCode.OK; |
||||
context.Response.ContentType = "text/plain;utf-8"; |
||||
await context.Response.Body.WriteAsync(rsp,0,rsp.Length); |
||||
} |
||||
|
||||
await _next.Invoke(context); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,9 @@ |
||||
{ |
||||
"Logging": { |
||||
"LogLevel": { |
||||
"Default": "Debug", |
||||
"System": "Information", |
||||
"Microsoft": "Information" |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,8 @@ |
||||
{ |
||||
"Logging": { |
||||
"LogLevel": { |
||||
"Default": "Warning" |
||||
} |
||||
}, |
||||
"AllowedHosts": "*" |
||||
} |
||||
Binary file not shown.
Binary file not shown.
@ -0,0 +1,12 @@ |
||||
using DotXxlJob.Core.Model; |
||||
|
||||
namespace DotXxlJob.Core |
||||
{ |
||||
public class CallbackTaskQueue |
||||
{ |
||||
public void Push(CallbackParam callbackParam) |
||||
{ |
||||
throw new System.NotImplementedException(); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,27 @@ |
||||
namespace DotXxlJob.Core.Config |
||||
{ |
||||
public class XxlJobExecutorOptions |
||||
{ |
||||
|
||||
public string AdminAddresses { get; set; } |
||||
|
||||
|
||||
public string AppName { get; set; } |
||||
|
||||
|
||||
public string SpecialBindAddress { get; set; } |
||||
|
||||
|
||||
public int Port { get; set; } |
||||
|
||||
|
||||
public string AccessToken { get; set; } |
||||
|
||||
|
||||
public string LogPath { get; set; } |
||||
|
||||
|
||||
public int LogRetentionDays { get; set; } |
||||
|
||||
} |
||||
} |
||||
@ -0,0 +1,42 @@ |
||||
using System; |
||||
|
||||
namespace DotXxlJob.Core |
||||
{ |
||||
internal static class Constants |
||||
{ |
||||
public const string XxlLogsDefaultRootDirectory = "xxl-job-logs"; |
||||
public const string HandleLogsDirectory = "HandlerLogs"; |
||||
public const string LogFileNameCallContextKey = "XxlJob.LogFileName"; |
||||
public const int DefaultLogRetentionDays = 30; |
||||
|
||||
public static TimeSpan RpcRequestExpireTimeSpan = TimeSpan.FromMinutes(3); |
||||
|
||||
public const int MaxCallbackRetryTimes = 10; |
||||
//每次回调最多发送几条记录 |
||||
public const int MaxCallbackRecordsPerRequest = 100; |
||||
public static TimeSpan CallbackRetryInterval = TimeSpan.FromSeconds(600); |
||||
|
||||
//Admin集群机器请求默认超时时间 |
||||
public static TimeSpan AdminServerDefaultTimeout = TimeSpan.FromSeconds(15); |
||||
//Admin集群中的某台机器熔断后间隔多长时间再重试 |
||||
public static TimeSpan AdminServerReconnectInterval = TimeSpan.FromMinutes(3); |
||||
//Admin集群中的某台机器请求失败多少次后熔断 |
||||
public const int AdminServerCircuitFaildTimes = 3; |
||||
|
||||
public static TimeSpan JobThreadWaitTime = TimeSpan.FromSeconds(90); |
||||
|
||||
public static class GlueType |
||||
{ |
||||
public const string BEAN = "BEAN"; |
||||
} |
||||
|
||||
public static class ExecutorBlockStrategy |
||||
{ |
||||
public const string SERIAL_EXECUTION = "SERIAL_EXECUTION"; |
||||
|
||||
public const string DISCARD_LATER = "DISCARD_LATER"; |
||||
|
||||
public const string COVER_EARLY = "COVER_EARLY"; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,35 @@ |
||||
using System; |
||||
|
||||
namespace DotXxlJob.Core |
||||
{ |
||||
public static class DateTimeExtensions |
||||
{ |
||||
private const long UnixEpochTicks = 621355968000000000; |
||||
private const long UnixEpochSeconds = 62135596800; |
||||
private const long UnixEpochMilliseconds = 62135596800000; |
||||
|
||||
public static DateTimeOffset FromUnixTimeSeconds(this long seconds) |
||||
{ |
||||
long ticks = seconds * TimeSpan.TicksPerSecond + UnixEpochTicks; |
||||
return new DateTime(ticks, DateTimeKind.Utc); |
||||
} |
||||
|
||||
public static DateTime FromUnixTimeMilliseconds(this long milliseconds) |
||||
{ |
||||
long ticks = milliseconds * TimeSpan.TicksPerMillisecond + UnixEpochTicks; |
||||
return new DateTime(ticks, DateTimeKind.Utc); |
||||
} |
||||
|
||||
public static long ToUnixTimeSeconds(this DateTime dateTime) |
||||
{ |
||||
long seconds = dateTime.ToUniversalTime().Ticks / TimeSpan.TicksPerSecond; |
||||
return seconds - UnixEpochSeconds; |
||||
} |
||||
|
||||
public static long ToUnixTimeMilliseconds(this DateTime dateTime) |
||||
{ |
||||
long milliseconds = dateTime.ToUniversalTime().Ticks / TimeSpan.TicksPerMillisecond; |
||||
return milliseconds - UnixEpochMilliseconds; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,10 @@ |
||||
namespace DotXxlJob.Core |
||||
{ |
||||
public class DefaultJobHandlerFactory:IJobHandlerFactory |
||||
{ |
||||
public IJobHandler GetJobHandler(string handlerName) |
||||
{ |
||||
return new HttpJobHandler(); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,18 @@ |
||||
using System.Threading.Tasks; |
||||
using DotXxlJob.Core.Model; |
||||
|
||||
namespace DotXxlJob.Core |
||||
{ |
||||
public class HttpJobHandler:IJobHandler |
||||
{ |
||||
public void Dispose() |
||||
{ |
||||
|
||||
} |
||||
|
||||
public Task<ReturnT> Execute(JobExecuteContext context) |
||||
{ |
||||
return Task.FromResult(ReturnT.SUCCESS); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,12 @@ |
||||
using System.Threading.Tasks; |
||||
using DotXxlJob.Core.Model; |
||||
|
||||
namespace DotXxlJob.Core |
||||
{ |
||||
public interface ITaskExecutor |
||||
{ |
||||
string GlueType { get; } |
||||
|
||||
Task<ReturnT> Execute(TriggerParam triggerParam); |
||||
} |
||||
} |
||||
@ -1,7 +1,20 @@ |
||||
using DotXxlJob.Core.Model; |
||||
|
||||
namespace DotXxlJob.Core |
||||
{ |
||||
public interface IXxlJobExecutor |
||||
{ |
||||
|
||||
ReturnT Beat(); |
||||
|
||||
|
||||
ReturnT IdleBeat(int jobId); |
||||
|
||||
|
||||
ReturnT Kill(int jobId); |
||||
|
||||
ReturnT Log(long logDateTim, int logId, int fromLineNum); |
||||
|
||||
|
||||
ReturnT Run(TriggerParam triggerParam); |
||||
} |
||||
} |
||||
@ -0,0 +1,130 @@ |
||||
using System; |
||||
using System.Collections.Concurrent; |
||||
using DotXxlJob.Core.Model; |
||||
using Microsoft.Extensions.Logging; |
||||
|
||||
namespace DotXxlJob.Core |
||||
{ |
||||
/// <summary> |
||||
/// 负责实际的JOB轮询 |
||||
/// </summary> |
||||
public class JobDispatcher |
||||
{ |
||||
private readonly TaskExecutorFactory _executorFactory; |
||||
private readonly CallbackTaskQueue _callbackTaskQueue; |
||||
|
||||
private readonly ConcurrentDictionary<int,JobQueue> RUNNING_QUEUE = new ConcurrentDictionary<int, JobQueue>(); |
||||
|
||||
|
||||
private readonly ILogger<JobQueue> _jobQueueLogger; |
||||
private readonly ILogger<JobDispatcher> _logger; |
||||
public JobDispatcher( |
||||
TaskExecutorFactory executorFactory, |
||||
CallbackTaskQueue callbackTaskQueue, |
||||
ILoggerFactory loggerFactory |
||||
) |
||||
{ |
||||
this. _executorFactory = executorFactory; |
||||
this. _callbackTaskQueue = callbackTaskQueue; |
||||
|
||||
|
||||
this._jobQueueLogger = loggerFactory.CreateLogger<JobQueue>(); |
||||
this._logger = loggerFactory.CreateLogger<JobDispatcher>(); |
||||
} |
||||
|
||||
|
||||
/// <summary> |
||||
/// 尝试移除JobTask |
||||
/// </summary> |
||||
/// <param name="jobId"></param> |
||||
/// <param name="reason"></param> |
||||
/// <returns></returns> |
||||
public bool TryRemoveJobTask(int jobId) |
||||
{ |
||||
if (RUNNING_QUEUE.TryGetValue(jobId, out var jobQueue)) |
||||
{ |
||||
jobQueue.Stop(); |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 执行队列,并快速返回结果 |
||||
/// </summary> |
||||
/// <param name="triggerParam"></param> |
||||
/// <returns></returns> |
||||
/// <exception cref="NotImplementedException"></exception> |
||||
public ReturnT Execute(TriggerParam triggerParam) |
||||
{ |
||||
|
||||
var executor = this._executorFactory.GetTaskExecutor(triggerParam.GlueType); |
||||
if (executor == null) |
||||
{ |
||||
return ReturnT.Failed($"glueType[{triggerParam.GlueType}] is not supported "); |
||||
} |
||||
|
||||
// 1. 根据JobId 获取 TaskQueue; 用于判断是否有正在执行的任务 |
||||
if (RUNNING_QUEUE.TryGetValue(triggerParam.JobId, out var taskQueue)) |
||||
{ |
||||
if (taskQueue.Executor != executor) //任务执行器变更 |
||||
{ |
||||
return ChangeJobQueue(triggerParam, executor); |
||||
} |
||||
} |
||||
|
||||
if (taskQueue != null) //旧任务还在执行,判断执行策略 |
||||
{ |
||||
//丢弃后续的 |
||||
if (Constants.ExecutorBlockStrategy.DISCARD_LATER == triggerParam.ExecutorBlockStrategy) |
||||
{ |
||||
return ReturnT.Failed($"block strategy effect:{triggerParam.ExecutorBlockStrategy}"); |
||||
} |
||||
//覆盖较早的 |
||||
if (Constants.ExecutorBlockStrategy.COVER_EARLY == triggerParam.ExecutorBlockStrategy) |
||||
{ |
||||
return taskQueue.Replace(triggerParam); |
||||
} |
||||
} |
||||
|
||||
return PushJobQueue(triggerParam, executor); |
||||
|
||||
} |
||||
|
||||
|
||||
/// <summary> |
||||
/// 等待检查 |
||||
/// </summary> |
||||
/// <param name="jobId"></param> |
||||
/// <returns></returns> |
||||
public ReturnT IdleBeat(int jobId) |
||||
{ |
||||
return RUNNING_QUEUE.ContainsKey(jobId) ? |
||||
new ReturnT(ReturnT.FAIL_CODE, "job thread is running or has trigger queue.") |
||||
: ReturnT.SUCCESS; |
||||
} |
||||
|
||||
|
||||
private ReturnT PushJobQueue(TriggerParam triggerParam, ITaskExecutor executor) |
||||
{ |
||||
JobQueue jobQueue = new JobQueue ( executor, this._callbackTaskQueue,this._jobQueueLogger); |
||||
if (RUNNING_QUEUE.TryAdd(triggerParam.JobId, jobQueue)) |
||||
{ |
||||
jobQueue.Push(triggerParam); |
||||
} |
||||
return ReturnT.Failed("add running queue executor error"); |
||||
} |
||||
|
||||
private ReturnT ChangeJobQueue(TriggerParam triggerParam, ITaskExecutor executor) |
||||
{ |
||||
|
||||
JobQueue jobQueue = new JobQueue ( executor, this._callbackTaskQueue,this._jobQueueLogger); |
||||
if (RUNNING_QUEUE.TryUpdate(triggerParam.JobId, jobQueue, null)) |
||||
{ |
||||
return jobQueue.Push(triggerParam); |
||||
} |
||||
return ReturnT.Failed(" replace running queue executor error"); |
||||
} |
||||
|
||||
} |
||||
} |
||||
@ -1,6 +1,6 @@ |
||||
namespace DotXxlJob.Core |
||||
{ |
||||
public class TaskExcutor |
||||
public class JobLogger |
||||
{ |
||||
|
||||
} |
||||
@ -0,0 +1,7 @@ |
||||
namespace DotXxlJob.Core |
||||
{ |
||||
public class LogResult |
||||
{ |
||||
|
||||
} |
||||
} |
||||
@ -0,0 +1,10 @@ |
||||
namespace DotXxlJob.Core.Model |
||||
{ |
||||
public class CallbackParam |
||||
{ |
||||
public CallbackParam(TriggerParam triggerParam, ReturnT result) |
||||
{ |
||||
throw new System.NotImplementedException(); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,11 @@ |
||||
using System.Collections.Generic; |
||||
using System.Runtime.Serialization; |
||||
|
||||
namespace DotXxlJob.Core.Model |
||||
{ |
||||
[DataContract(Name = "hessianArrayList")] |
||||
public class HessianArrayList:List<object> |
||||
{ |
||||
|
||||
} |
||||
} |
||||
@ -0,0 +1,7 @@ |
||||
namespace DotXxlJob.Core.Model |
||||
{ |
||||
public class JobExecuteContext |
||||
{ |
||||
|
||||
} |
||||
} |
||||
@ -0,0 +1,42 @@ |
||||
using System.Runtime.Serialization; |
||||
|
||||
namespace DotXxlJob.Core |
||||
{ |
||||
[DataContract] |
||||
public class ReturnT |
||||
{ |
||||
public const int SUCCESS_CODE = 200; |
||||
public const int FAIL_CODE = 500; |
||||
|
||||
public static readonly ReturnT SUCCESS = new ReturnT(SUCCESS_CODE, null); |
||||
public static readonly ReturnT FAIL = new ReturnT(FAIL_CODE, null); |
||||
public static readonly ReturnT FAIL_TIMEOUT = new ReturnT(502, null); |
||||
|
||||
[DataMember(Name = "code",Order = 1)] |
||||
public int Code { get; set; } |
||||
[DataMember(Name = "msg",Order = 2)] |
||||
public string Msg { get; set; } |
||||
|
||||
public ReturnT() { } |
||||
|
||||
public ReturnT(int code, string msg) |
||||
{ |
||||
this.Code = code; |
||||
this.Msg = msg; |
||||
} |
||||
|
||||
public static ReturnT Failed(string msg) |
||||
{ |
||||
return new ReturnT(FAIL_CODE, msg); |
||||
} |
||||
public static ReturnT Success(string msg) |
||||
{ |
||||
return new ReturnT(SUCCESS_CODE, msg); |
||||
} |
||||
[DataMember(Name = "content",Order = 3)] |
||||
public object Content { get; set; } |
||||
} |
||||
|
||||
|
||||
|
||||
} |
||||
@ -1,7 +1,51 @@ |
||||
using System.Collections; |
||||
using System.Collections.Specialized; |
||||
using System.Runtime.Serialization; |
||||
|
||||
namespace DotXxlJob.Core.Model |
||||
{ |
||||
[DataContract(Name = "com.xxl.rpc.remoting.net.params.XxlRpcRequest")] |
||||
public class RpcRequest |
||||
{ |
||||
/* |
||||
requestId |
||||
createMillisTime |
||||
accessToken |
||||
className |
||||
methodName |
||||
version |
||||
parameterTypes |
||||
parameters |
||||
*/ |
||||
[DataMember(Name = "requestId",Order = 1)] |
||||
public string RequestId { get; set; } |
||||
|
||||
//[DataMember(Name = "serverAddress")] |
||||
//public string ServerAddress{ get; set; } |
||||
|
||||
[DataMember(Name = "createMillisTime" ,Order = 2)] |
||||
public long CreateMillisTime{ get; set; } |
||||
|
||||
|
||||
[DataMember(Name = "accessToken" ,Order = 3)] |
||||
public string AccessToken{ get; set; } |
||||
|
||||
[DataMember(Name = "className" ,Order = 4)] |
||||
public string ClassName{ get; set; } |
||||
|
||||
[DataMember(Name = "methodName" ,Order = 5)] |
||||
public string MethodName{ get; set; } |
||||
|
||||
[DataMember(Name = "version" ,Order = 6)] |
||||
public string Version{ get; set; } |
||||
|
||||
[DataMember(Name = "parameterTypes",Order = 7)] |
||||
public HessianArrayList ParameterTypes{ get; set; } |
||||
|
||||
|
||||
[DataMember(Name = "parameters",Order = 8)] |
||||
public HessianArrayList Parameters{ get; set; } |
||||
|
||||
|
||||
} |
||||
} |
||||
@ -1,7 +1,18 @@ |
||||
using System.Runtime.Serialization; |
||||
|
||||
namespace DotXxlJob.Core.Model |
||||
{ |
||||
[DataContract(Name = "com.xxl.rpc.remoting.net.params.XxlRpcResponse")] |
||||
public class RpcResponse |
||||
{ |
||||
[DataMember(Name = "requestId",Order = 1)] |
||||
public string RequestId{ get; set; } |
||||
[DataMember(Name = "errorMsg",Order = 2)] |
||||
public string ErrorMsg; |
||||
[DataMember(Name = "result",Order = 3)] |
||||
public object Result{ get; set; } |
||||
|
||||
|
||||
public bool IsError => this.ErrorMsg != null; |
||||
} |
||||
} |
||||
@ -1,7 +1,45 @@ |
||||
using System.Runtime.Serialization; |
||||
|
||||
namespace DotXxlJob.Core.Model |
||||
{ |
||||
|
||||
[DataContract(Name = "triggerParam")] |
||||
public class TriggerParam |
||||
{ |
||||
static readonly long SerialVersionUID = 42L; |
||||
|
||||
[DataMember(Name = "jobId")] |
||||
public int JobId { get; set; } |
||||
|
||||
[DataMember(Name = "executorHandler")] |
||||
public string ExecutorHandler { get; set; } |
||||
[DataMember(Name = "executorParams")] |
||||
public string ExecutorParams{ get; set; } |
||||
|
||||
[DataMember(Name = "executorBlockStrategy")] |
||||
public string ExecutorBlockStrategy{ get; set; } |
||||
|
||||
[DataMember(Name = "executorTimeout")] |
||||
public int ExecutorTimeout{ get; set; } |
||||
|
||||
[DataMember(Name = "logId")] |
||||
public int LogId{ get; set; } |
||||
[DataMember(Name = "logDateTim")] |
||||
public long LogDateTime{ get; set; } |
||||
|
||||
|
||||
[DataMember(Name = "glueType")] |
||||
public string GlueType{ get; set; } |
||||
|
||||
[DataMember(Name = "glueSource")] |
||||
public string GlueSource{ get; set; } |
||||
|
||||
[DataMember(Name = "glueUpdatetime")] |
||||
public long GlueUpdateTime{ get; set; } |
||||
|
||||
[DataMember(Name = "broadcastIndex")] |
||||
public int BroadcastIndex{ get; set; } |
||||
[DataMember(Name = "broadcastTotal")] |
||||
public int BroadcastTotal{ get; set; } |
||||
} |
||||
} |
||||
@ -1,11 +0,0 @@ |
||||
namespace DotXxlJob.Core |
||||
{ |
||||
public class ReturnT<T> |
||||
{ |
||||
public int Code { get; set; } |
||||
|
||||
public string Msg { get; set; } |
||||
|
||||
public T Content { get; set; } |
||||
} |
||||
} |
||||
@ -0,0 +1,20 @@ |
||||
using Microsoft.Extensions.DependencyInjection; |
||||
|
||||
namespace DotXxlJob.Core |
||||
{ |
||||
public static class ServiceCollectionExtensions |
||||
{ |
||||
public static IServiceCollection AddXxlJobExecutor(this IServiceCollection services) |
||||
{ |
||||
services.AddSingleton<ITaskExecutor, TaskExecutors.BeanTaskExecutor>(); |
||||
services.AddSingleton<IJobHandlerFactory,DefaultJobHandlerFactory >(); |
||||
services.AddSingleton<JobDispatcher>(); |
||||
services.AddSingleton<TaskExecutorFactory>(); |
||||
services.AddSingleton<XxlRpcServiceHandler>(); |
||||
services.AddSingleton<CallbackTaskQueue>(); |
||||
services.AddSingleton<IExecutorRegistry, ExecutorRegistry>(); |
||||
|
||||
return services; |
||||
} |
||||
} |
||||
} |
||||
@ -1,7 +1,40 @@ |
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using Microsoft.Extensions.DependencyInjection; |
||||
|
||||
namespace DotXxlJob.Core |
||||
{ |
||||
/// <summary> |
||||
/// 负责响应RPC请求,调度任务执行器的工厂类 |
||||
/// </summary> |
||||
public class TaskExecutorFactory |
||||
{ |
||||
|
||||
private readonly IServiceProvider _provider; |
||||
|
||||
private readonly Dictionary<string, ITaskExecutor> _cache = new Dictionary<string, ITaskExecutor>(); |
||||
public TaskExecutorFactory(IServiceProvider provider) |
||||
{ |
||||
_provider = provider; |
||||
Initialize(); |
||||
} |
||||
|
||||
private void Initialize() |
||||
{ |
||||
var executors = _provider.GetServices<ITaskExecutor>(); |
||||
|
||||
if (executors != null && executors.Any()) |
||||
{ |
||||
foreach (var item in executors) |
||||
{ |
||||
_cache.Add(item.GlueType,item); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public ITaskExecutor GetTaskExecutor(string glueType) |
||||
{ |
||||
return _cache.TryGetValue(glueType, out var executor) ? executor : null; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,34 @@ |
||||
using System.Threading.Tasks; |
||||
using DotXxlJob.Core.Model; |
||||
|
||||
namespace DotXxlJob.Core.TaskExecutors |
||||
{ |
||||
/// <summary> |
||||
/// 实现 IJobHandler的执行器 |
||||
/// </summary> |
||||
public class BeanTaskExecutor:ITaskExecutor |
||||
{ |
||||
private readonly IJobHandlerFactory _handlerFactory; |
||||
|
||||
public BeanTaskExecutor(IJobHandlerFactory handlerFactory) |
||||
{ |
||||
_handlerFactory = handlerFactory; |
||||
} |
||||
|
||||
public string GlueType { get; } = Constants.GlueType.BEAN; |
||||
|
||||
public Task<ReturnT> Execute(TriggerParam triggerParam) |
||||
{ |
||||
var handler = _handlerFactory.GetJobHandler(triggerParam.ExecutorHandler); |
||||
|
||||
if (handler == null) |
||||
{ |
||||
|
||||
return Task.FromResult(ReturnT.Failed($"job handler [{triggerParam.ExecutorHandler} not found.")); |
||||
} |
||||
|
||||
return Task.FromResult(ReturnT.Success("OK")); |
||||
//return handler.Execute(new JobExecuteContext()); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,116 @@ |
||||
using System; |
||||
using System.Collections.Concurrent; |
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
using DotXxlJob.Core.Model; |
||||
using Microsoft.Extensions.Logging; |
||||
|
||||
namespace DotXxlJob.Core |
||||
{ |
||||
public class JobQueue |
||||
{ |
||||
private readonly ITaskExecutor _executor; |
||||
private readonly CallbackTaskQueue _callbackTaskQueue; |
||||
private readonly ILogger<JobQueue> _logger; |
||||
private readonly ConcurrentQueue<TriggerParam> TASK_QUEUE = new ConcurrentQueue<TriggerParam>(); |
||||
public JobQueue(ITaskExecutor executor,CallbackTaskQueue callbackTaskQueue,ILogger<JobQueue> logger) |
||||
{ |
||||
_executor = executor; |
||||
_callbackTaskQueue = callbackTaskQueue; |
||||
_logger = logger; |
||||
} |
||||
|
||||
public ITaskExecutor Executor => this._executor; |
||||
|
||||
|
||||
private CancellationTokenSource _cancellationTokenSource; |
||||
|
||||
/// <summary> |
||||
/// 覆盖之前的队列 |
||||
/// </summary> |
||||
/// <param name="triggerParam"></param> |
||||
/// <returns></returns> |
||||
public ReturnT Replace(TriggerParam triggerParam) |
||||
{ |
||||
Stop(); |
||||
while (!TASK_QUEUE.IsEmpty) |
||||
{ |
||||
TASK_QUEUE.TryDequeue(out _); |
||||
} |
||||
|
||||
return Push(triggerParam); |
||||
} |
||||
|
||||
public ReturnT Push(TriggerParam triggerParam) |
||||
{ |
||||
this.TASK_QUEUE.Enqueue(triggerParam); |
||||
StartTask(); |
||||
return ReturnT.SUCCESS; |
||||
} |
||||
|
||||
public void Stop() |
||||
{ |
||||
this._cancellationTokenSource?.Cancel(); |
||||
this._cancellationTokenSource?.Dispose(); |
||||
this._cancellationTokenSource = null; |
||||
|
||||
} |
||||
|
||||
|
||||
private void StartTask() |
||||
{ |
||||
if (this._cancellationTokenSource != null ) |
||||
{ |
||||
return; //running |
||||
} |
||||
this._cancellationTokenSource =new CancellationTokenSource(); |
||||
CancellationToken ct = _cancellationTokenSource.Token; |
||||
|
||||
Task.Factory.StartNew(async () => |
||||
{ |
||||
|
||||
//ct.ThrowIfCancellationRequested(); |
||||
|
||||
while (!ct.IsCancellationRequested) |
||||
{ |
||||
if (TASK_QUEUE.IsEmpty) |
||||
{ |
||||
break; |
||||
} |
||||
|
||||
ReturnT result = null; |
||||
TriggerParam triggerParam = null; |
||||
try |
||||
{ |
||||
|
||||
if (TASK_QUEUE.TryDequeue(out triggerParam)) |
||||
{ |
||||
result = await this._executor.Execute(triggerParam); |
||||
} |
||||
else |
||||
{ |
||||
this._logger.LogWarning("Dequeue Task Failed"); |
||||
} |
||||
} |
||||
catch (Exception ex) |
||||
{ |
||||
result = ReturnT.Failed("Dequeue Task Failed:"+ex.Message); |
||||
} |
||||
|
||||
if(triggerParam !=null) |
||||
{ |
||||
this._callbackTaskQueue.Push(new CallbackParam(triggerParam, result)); |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
this._cancellationTokenSource.Dispose(); |
||||
this._cancellationTokenSource = null; |
||||
|
||||
}, this._cancellationTokenSource.Token); |
||||
|
||||
|
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,212 @@ |
||||
using System; |
||||
using System.Collections.Concurrent; |
||||
using System.IO; |
||||
using System.Reflection; |
||||
using System.Threading.Tasks; |
||||
using DotXxlJob.Core.Config; |
||||
using DotXxlJob.Core.Model; |
||||
using Hessian.Net; |
||||
using Microsoft.Extensions.Logging; |
||||
using Microsoft.Extensions.Options; |
||||
|
||||
namespace DotXxlJob.Core |
||||
{ |
||||
|
||||
/// <summary> |
||||
/// 负责执行Http请求,序列化和反序列化并发送响应 |
||||
/// </summary> |
||||
public class XxlRpcServiceHandler |
||||
{ |
||||
|
||||
private readonly JobDispatcher _jobDispatcher; |
||||
private readonly ILogger<XxlRpcServiceHandler> _logger; |
||||
private readonly DataContractHessianSerializer _reqSerializer; |
||||
private readonly DataContractHessianSerializer _resSerializer; |
||||
private readonly XxlJobExecutorOptions _options; |
||||
|
||||
private readonly ConcurrentDictionary<string, MethodInfo> METHOD_CACHE = |
||||
new ConcurrentDictionary<string, MethodInfo>(); |
||||
|
||||
public XxlRpcServiceHandler(IOptions<XxlJobExecutorOptions> optionsAccessor, |
||||
JobDispatcher jobDispatcher, |
||||
ILogger<XxlRpcServiceHandler> logger) |
||||
{ |
||||
|
||||
_jobDispatcher = jobDispatcher; |
||||
this._logger = logger; |
||||
this._reqSerializer = new DataContractHessianSerializer(typeof (RpcRequest)); |
||||
this._resSerializer = new DataContractHessianSerializer(typeof (RpcResponse)); |
||||
this._options = optionsAccessor.Value; |
||||
if (this._options == null) |
||||
{ |
||||
throw new ArgumentNullException(nameof(XxlJobExecutorOptions)); |
||||
} |
||||
|
||||
} |
||||
|
||||
/// <summary> |
||||
/// 处理XxlRpc请求流 |
||||
/// </summary> |
||||
/// <param name="reqStream"></param> |
||||
/// <returns></returns> |
||||
/// <exception cref="NotImplementedException"></exception> |
||||
public async Task<byte[]> HandlerAsync(Stream reqStream) |
||||
{ |
||||
using (Stream output = File.OpenWrite(DateTime.Now.ToUnixTimeSeconds()+".dat")) |
||||
{ |
||||
reqStream.CopyTo(output); |
||||
} |
||||
|
||||
var req = _reqSerializer.ReadObject(reqStream) as RpcRequest; |
||||
var res = new RpcResponse(); |
||||
if (!ValidRequest(req, out var error)) |
||||
{ |
||||
res.ErrorMsg = error; |
||||
} |
||||
else |
||||
{ |
||||
await Invoke(req, res); |
||||
} |
||||
|
||||
using (var outputStream = new MemoryStream()) |
||||
{ |
||||
_resSerializer.WriteObject(outputStream,res); |
||||
return outputStream.GetBuffer(); |
||||
} |
||||
|
||||
} |
||||
|
||||
/// <summary> |
||||
/// 校验请求信息 |
||||
/// </summary> |
||||
/// <param name="req"></param> |
||||
/// <param name="error"></param> |
||||
/// <returns></returns> |
||||
private bool ValidRequest(RpcRequest req,out string error) |
||||
{ |
||||
error = string.Empty; |
||||
if (req == null) |
||||
{ |
||||
error = "unknown request stream data,codec fail"; |
||||
return false; |
||||
} |
||||
|
||||
if (!"com.xxl.job.core.biz.ExecutorBiz".Equals(req.ClassName)) // |
||||
{ |
||||
error = "not supported request!"; |
||||
return false; |
||||
} |
||||
|
||||
if (DateTime.UtcNow.Subtract(req.CreateMillisTime.FromUnixTimeMilliseconds()) > Constants.RpcRequestExpireTimeSpan) |
||||
{ |
||||
error = "request is timeout!"; |
||||
return false; |
||||
} |
||||
|
||||
if (!string.IsNullOrEmpty(this._options.AccessToken) && this._options.AccessToken != req.AccessToken) |
||||
{ |
||||
error = "need authorize"; |
||||
return false; |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 执行请求,获取执行函数 |
||||
/// </summary> |
||||
/// <param name="req"></param> |
||||
/// <param name="res"></param> |
||||
/// <returns></returns> |
||||
private Task Invoke(RpcRequest req, RpcResponse res) |
||||
{ |
||||
try |
||||
{ |
||||
var method = GetMethodInfo(req.MethodName); |
||||
if (method == null) |
||||
{ |
||||
res.ErrorMsg = $"The method{req.MethodName} is not defined."; |
||||
} |
||||
else |
||||
{ |
||||
var result = method.Invoke(this, req.Parameters.ToArray()); |
||||
|
||||
res.Result = result; |
||||
} |
||||
|
||||
} |
||||
catch (Exception ex) |
||||
{ |
||||
res.ErrorMsg = ex.ToString(); |
||||
} |
||||
|
||||
return Task.CompletedTask; |
||||
|
||||
} |
||||
|
||||
private MethodInfo GetMethodInfo(string methodName) |
||||
{ |
||||
if (METHOD_CACHE.TryGetValue(methodName, out var method)) |
||||
{ |
||||
return method; |
||||
} |
||||
|
||||
var type = GetType(); |
||||
method = type.GetMethod( methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.IgnoreCase); |
||||
if (method != null) |
||||
{ |
||||
METHOD_CACHE.TryAdd(methodName, method); |
||||
} |
||||
|
||||
return method; |
||||
} |
||||
|
||||
|
||||
#region rpc service |
||||
|
||||
private ReturnT Beat() |
||||
{ |
||||
return ReturnT.SUCCESS; |
||||
} |
||||
|
||||
private ReturnT IdleBeat(int jobId) |
||||
{ |
||||
return this._jobDispatcher.IdleBeat(jobId); |
||||
} |
||||
|
||||
private ReturnT Kill(int jobId) |
||||
{ |
||||
return this._jobDispatcher.TryRemoveJobTask(jobId) ? |
||||
ReturnT.SUCCESS |
||||
: |
||||
ReturnT.Success("job thread already killed."); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// TODO:获取执行日志 |
||||
/// </summary> |
||||
/// <param name="logDateTime"></param> |
||||
/// <param name="logId"></param> |
||||
/// <param name="fromLineNum"></param> |
||||
/// <returns></returns> |
||||
private ReturnT Log(long logDateTime, int logId, int fromLineNum) |
||||
{ |
||||
//var logResult = JobLogger.ReadLog(logDateTime, logId, fromLineNum); |
||||
return ReturnT.Success(null); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 执行 |
||||
/// </summary> |
||||
/// <param name="triggerParam"></param> |
||||
/// <returns></returns> |
||||
private ReturnT Run(TriggerParam triggerParam) |
||||
{ |
||||
return this._jobDispatcher.Execute(triggerParam); |
||||
} |
||||
#endregion |
||||
|
||||
|
||||
|
||||
} |
||||
} |
||||
@ -0,0 +1,167 @@ |
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.IO; |
||||
|
||||
namespace Hessian.Net |
||||
{ |
||||
public class ListElement: ISerializationElement |
||||
{ |
||||
public ListElement(Type listType) |
||||
{ |
||||
this.ObjectType = listType.GetElementType(); |
||||
} |
||||
|
||||
public Type ObjectType { get; } |
||||
public void Serialize(HessianOutputWriter writer, object graph, HessianSerializationContext context) |
||||
{ |
||||
throw new NotImplementedException(); |
||||
} |
||||
|
||||
public object Deserialize(HessianInputReader reader, HessianSerializationContext context) |
||||
{ |
||||
var preamble = reader.BeginList(); |
||||
switch (preamble) |
||||
{ |
||||
case ObjectPreamble.FixList: |
||||
break; |
||||
case ObjectPreamble.VarList: |
||||
break; |
||||
case ObjectPreamble.FixListUntyped: |
||||
break; |
||||
case ObjectPreamble.VarListUntyped: |
||||
break; |
||||
case ObjectPreamble.CompactFixList: |
||||
break; |
||||
case ObjectPreamble.CompactFixListUntyped: |
||||
break; |
||||
} |
||||
|
||||
|
||||
reader.EndList(); |
||||
} |
||||
|
||||
|
||||
private string ReadTypeName(HessianInputReader reader) |
||||
{ |
||||
reader.re |
||||
var tag = reader.Peek(); |
||||
|
||||
if (!tag.HasValue) { |
||||
throw new EndOfStreamException(); |
||||
} |
||||
|
||||
// A type name is either a string, or an integer reference to a |
||||
// string already read and stored in the type-name ref map. |
||||
if ((tag >= 0x00 && tag < 0x20) |
||||
|| (tag >= 0x30 && tag < 0x34) |
||||
|| tag == 0x52 |
||||
|| tag == 0x53) { |
||||
var typeName = ReadString(); |
||||
typeNameRefs.Add(typeName); |
||||
return typeName; |
||||
} |
||||
|
||||
return typeNameRefs.Get(ReadInteger()); |
||||
} |
||||
|
||||
#region List |
||||
|
||||
private IList<object> ReadVarList() |
||||
{ |
||||
reader.ReadByte(); |
||||
var type = ReadTypeName(); |
||||
return ReadListCore(type: type); |
||||
} |
||||
|
||||
private IList<object> ReadFixList() |
||||
{ |
||||
reader.ReadByte(); |
||||
var type = ReadTypeName(); |
||||
var length = ReadInteger(); |
||||
return ReadListCore(length, type); |
||||
} |
||||
|
||||
private IList<object> ReadVarListUntyped() |
||||
{ |
||||
reader.ReadByte(); |
||||
return ReadListCore(); |
||||
} |
||||
|
||||
private IList<object> ReadFixListUntyped() |
||||
{ |
||||
reader.ReadByte(); |
||||
var length = ReadInteger(); |
||||
return ReadListCore(length); |
||||
} |
||||
|
||||
private IList<object> ReadCompactFixList() |
||||
{ |
||||
var tag = reader.ReadByte(); |
||||
var length = tag - 0x70; |
||||
var type = ReadTypeName(); |
||||
return ReadListCore(length, type); |
||||
} |
||||
|
||||
private IList<object> ReadCompactFixListUntyped() |
||||
{ |
||||
var tag = reader.ReadByte(); |
||||
var length = tag - 0x70; |
||||
return ReadListCore(length); |
||||
} |
||||
|
||||
private IList<object> ReadListCore(int? length = null, string type = null) |
||||
{ |
||||
var list = GetListIntance(type, length); |
||||
|
||||
objectRefs.Add(list); |
||||
|
||||
if (length.HasValue) { |
||||
PopulateFixLengthList(list, length.Value); |
||||
} else { |
||||
PopulateVarList(list); |
||||
} |
||||
return list; |
||||
} |
||||
|
||||
private IList<object> GetListIntance(string type, int? length = null) |
||||
{ |
||||
IList<object> list; |
||||
|
||||
if (length.HasValue) { |
||||
if (!listTypeResolver.Value.TryGetListInstance(type, length.Value, out list)) { |
||||
list = new List<object>(length.Value); |
||||
} |
||||
} else { |
||||
if (!listTypeResolver.Value.TryGetListInstance(type, out list)) { |
||||
list = new List<object>(); |
||||
} |
||||
} |
||||
|
||||
return list; |
||||
} |
||||
|
||||
private void PopulateFixLengthList(IList<object> list, int length) |
||||
{ |
||||
for (var i = 0; i < length; ++i) { |
||||
list.Add(ReadValue()); |
||||
} |
||||
} |
||||
|
||||
private void PopulateVarList(IList<object> list) |
||||
{ |
||||
while (true) { |
||||
var tag = reader.Peek(); |
||||
if (!tag.HasValue) { |
||||
throw new EndOfStreamException(); |
||||
} |
||||
if (tag == 'Z') { |
||||
reader.ReadByte(); |
||||
break; |
||||
} |
||||
list.Add(ReadValue()); |
||||
} |
||||
} |
||||
|
||||
#endregion |
||||
} |
||||
} |
||||
@ -0,0 +1,13 @@ |
||||
namespace Hessian.Net |
||||
{ |
||||
public enum ListPreamble |
||||
{ |
||||
None, |
||||
VarList, |
||||
FixList, |
||||
VarListUntyped, |
||||
FixListUntyped, |
||||
CompactFixList, |
||||
CompactFixListUntyped |
||||
} |
||||
} |
||||
@ -0,0 +1,892 @@ |
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.IO; |
||||
using System.Text; |
||||
|
||||
using Hessian.Collections; |
||||
using Hessian.Platform; |
||||
|
||||
namespace Hessian |
||||
{ |
||||
public class Deserializer |
||||
{ |
||||
private readonly ValueReader reader; |
||||
private readonly IRefMap<ClassDef> classDefs; |
||||
private readonly IRefMap<object> objectRefs; |
||||
private readonly IRefMap<string> typeNameRefs; |
||||
private readonly Lazy<ListTypeResolver> listTypeResolver = new Lazy<ListTypeResolver>(); |
||||
private readonly Lazy<DictionaryTypeResolver> dictTypeResolver = new Lazy<DictionaryTypeResolver>(); |
||||
|
||||
private static readonly EndianBitConverter BitConverter = new BigEndianBitConverter(); |
||||
|
||||
public Deserializer (Stream stream) |
||||
{ |
||||
if (stream == null) { |
||||
throw new ArgumentNullException("stream"); |
||||
} |
||||
|
||||
reader = new ValueReader(stream); |
||||
classDefs = new ListRefMap<ClassDef>(); |
||||
objectRefs = new ListRefMap<object>(); |
||||
typeNameRefs = new ListRefMap<string>(); |
||||
} |
||||
|
||||
#region ReadValue |
||||
|
||||
public object ReadValue () |
||||
{ |
||||
var tag = reader.Peek (); |
||||
|
||||
if (!tag.HasValue) { |
||||
throw new EndOfStreamException(); |
||||
} |
||||
|
||||
switch (tag.Value) { |
||||
case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: |
||||
case 0x08: case 0x09: case 0x0A: case 0x0B: case 0x0C: case 0x0D: case 0x0E: case 0x0F: |
||||
case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: |
||||
case 0x18: case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: case 0x1E: case 0x1F: |
||||
return ReadShortString(); |
||||
|
||||
case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27: |
||||
case 0x28: case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2E: case 0x2F: |
||||
return ReadShortBinary(); |
||||
|
||||
case 0x30: case 0x31: case 0x32: case 0x33: |
||||
return ReadMediumString(); |
||||
|
||||
case 0x34: case 0x35: case 0x36: case 0x37: |
||||
return ReadMediumBinary(); |
||||
|
||||
case 0x38: case 0x39: case 0x3A: case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F: |
||||
return ReadLongThreeBytes(); |
||||
|
||||
case 0x40: |
||||
return Reserved(); |
||||
|
||||
case 0x41: case 0x42: |
||||
return ReadChunkedBinary(); |
||||
|
||||
case 0x43: |
||||
return ReadClassDefinition(); |
||||
|
||||
case 0x44: |
||||
return ReadFullDouble(); |
||||
|
||||
case 0x45: |
||||
return Reserved(); |
||||
|
||||
case 0x46: |
||||
return ReadBoolean(); |
||||
|
||||
case 0x47: |
||||
return Reserved(); |
||||
|
||||
case 0x48: |
||||
return ReadUntypedMap(); |
||||
|
||||
case 0x49: |
||||
return ReadInteger(); |
||||
|
||||
case 0x4A: |
||||
return ReadDateInMillis(); |
||||
|
||||
case 0x4B: |
||||
return ReadDateInMinutes(); |
||||
|
||||
case 0x4C: |
||||
return ReadLongFull(); |
||||
|
||||
case 0x4D: |
||||
return ReadTypedMap(); |
||||
|
||||
case 0x4E: |
||||
return ReadNull(); |
||||
|
||||
case 0x4F: |
||||
return ReadObject(); |
||||
|
||||
case 0x50: |
||||
return Reserved(); |
||||
|
||||
case 0x51: |
||||
return ReadRef(); |
||||
|
||||
case 0x52: case 0x53: |
||||
return ReadChunkedString(); |
||||
|
||||
case 0x54: |
||||
return ReadBoolean(); |
||||
|
||||
case 0x55: |
||||
return ReadVarList(); |
||||
|
||||
case 0x56: |
||||
return ReadFixList(); |
||||
|
||||
case 0x57: |
||||
return ReadVarListUntyped(); |
||||
|
||||
case 0x58: |
||||
return ReadFixListUntyped(); |
||||
|
||||
case 0x59: |
||||
return ReadLongFourBytes(); |
||||
|
||||
case 0x5A: |
||||
// List terminator - solitary list terminators are most definitely not legit. |
||||
throw new UnexpectedTagException(0x5A, "value"); |
||||
|
||||
case 0x5B: case 0x5C: |
||||
return ReadDoubleOneByte(); |
||||
|
||||
case 0x5D: |
||||
return ReadDoubleOneByte(); |
||||
|
||||
case 0x5E: |
||||
return ReadDoubleTwoBytes(); |
||||
|
||||
case 0x5F: |
||||
return ReadDoubleFourBytes(); |
||||
|
||||
case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67: |
||||
case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F: |
||||
return ReadObjectCompact(); |
||||
|
||||
case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77: |
||||
return ReadCompactFixList(); |
||||
|
||||
case 0x78: case 0x79: case 0x7A: case 0x7B: case 0x7C: case 0x7D: case 0x7E: case 0x7F: |
||||
return ReadCompactFixListUntyped(); |
||||
|
||||
case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87: |
||||
case 0x88: case 0x89: case 0x8A: case 0x8B: case 0x8C: case 0x8D: case 0x8E: case 0x8F: |
||||
case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97: |
||||
case 0x98: case 0x99: case 0x9A: case 0x9B: case 0x9C: case 0x9D: case 0x9E: case 0x9F: |
||||
case 0xA0: case 0xA1: case 0xA2: case 0xA3: case 0xA4: case 0xA5: case 0xA6: case 0xA7: |
||||
case 0xA8: case 0xA9: case 0xAA: case 0xAB: case 0xAC: case 0xAD: case 0xAE: case 0xAF: |
||||
case 0xB0: case 0xB1: case 0xB2: case 0xB3: case 0xB4: case 0xB5: case 0xB6: case 0xB7: |
||||
case 0xB8: case 0xB9: case 0xBA: case 0xBB: case 0xBC: case 0xBD: case 0xBE: case 0xBF: |
||||
return ReadIntegerSingleByte(); |
||||
|
||||
case 0xC0: case 0xC1: case 0xC2: case 0xC3: case 0xC4: case 0xC5: case 0xC6: case 0xC7: |
||||
case 0xC8: case 0xC9: case 0xCA: case 0xCB: case 0xCC: case 0xCD: case 0xCE: case 0xCF: |
||||
return ReadIntegerTwoBytes(); |
||||
|
||||
case 0xD0: case 0xD1: case 0xD2: case 0xD3: case 0xD4: case 0xD5: case 0xD6: case 0xD7: |
||||
return ReadIntegerThreeBytes(); |
||||
|
||||
case 0xD8: case 0xD9: case 0xDA: case 0xDB: case 0xDC: case 0xDD: case 0xDE: case 0xDF: |
||||
case 0xE0: case 0xE1: case 0xE2: case 0xE3: case 0xE4: case 0xE5: case 0xE6: case 0xE7: |
||||
case 0xE8: case 0xE9: case 0xEA: case 0xEB: case 0xEC: case 0xED: case 0xEE: case 0xEF: |
||||
return ReadLongOneByte(); |
||||
|
||||
case 0xF0: case 0xF1: case 0xF2: case 0xF3: case 0xF4: case 0xF5: case 0xF6: case 0xF7: |
||||
case 0xF8: case 0xF9: case 0xFA: case 0xFB: case 0xFC: case 0xFD: case 0xFE: case 0xFF: |
||||
return ReadLongTwoBytes(); |
||||
} |
||||
|
||||
|
||||
throw new Exception("WTF: byte value " + tag.Value + " not accounted for!"); |
||||
} |
||||
|
||||
#endregion |
||||
|
||||
private string ReadTypeName() |
||||
{ |
||||
var tag = reader.Peek(); |
||||
|
||||
if (!tag.HasValue) { |
||||
throw new EndOfStreamException(); |
||||
} |
||||
|
||||
// A type name is either a string, or an integer reference to a |
||||
// string already read and stored in the type-name ref map. |
||||
if ((tag >= 0x00 && tag < 0x20) |
||||
|| (tag >= 0x30 && tag < 0x34) |
||||
|| tag == 0x52 |
||||
|| tag == 0x53) { |
||||
var typeName = ReadString(); |
||||
typeNameRefs.Add(typeName); |
||||
return typeName; |
||||
} |
||||
|
||||
return typeNameRefs.Get(ReadInteger()); |
||||
} |
||||
|
||||
#region List |
||||
|
||||
private IList<object> ReadVarList() |
||||
{ |
||||
reader.ReadByte(); |
||||
var type = ReadTypeName(); |
||||
return ReadListCore(type: type); |
||||
} |
||||
|
||||
private IList<object> ReadFixList() |
||||
{ |
||||
reader.ReadByte(); |
||||
var type = ReadTypeName(); |
||||
var length = ReadInteger(); |
||||
return ReadListCore(length, type); |
||||
} |
||||
|
||||
private IList<object> ReadVarListUntyped() |
||||
{ |
||||
reader.ReadByte(); |
||||
return ReadListCore(); |
||||
} |
||||
|
||||
private IList<object> ReadFixListUntyped() |
||||
{ |
||||
reader.ReadByte(); |
||||
var length = ReadInteger(); |
||||
return ReadListCore(length); |
||||
} |
||||
|
||||
private IList<object> ReadCompactFixList() |
||||
{ |
||||
var tag = reader.ReadByte(); |
||||
var length = tag - 0x70; |
||||
var type = ReadTypeName(); |
||||
return ReadListCore(length, type); |
||||
} |
||||
|
||||
private IList<object> ReadCompactFixListUntyped() |
||||
{ |
||||
var tag = reader.ReadByte(); |
||||
var length = tag - 0x70; |
||||
return ReadListCore(length); |
||||
} |
||||
|
||||
private IList<object> ReadListCore(int? length = null, string type = null) |
||||
{ |
||||
var list = GetListIntance(type, length); |
||||
|
||||
objectRefs.Add(list); |
||||
|
||||
if (length.HasValue) { |
||||
PopulateFixLengthList(list, length.Value); |
||||
} else { |
||||
PopulateVarList(list); |
||||
} |
||||
return list; |
||||
} |
||||
|
||||
private IList<object> GetListIntance(string type, int? length = null) |
||||
{ |
||||
IList<object> list; |
||||
|
||||
if (length.HasValue) { |
||||
if (!listTypeResolver.Value.TryGetListInstance(type, length.Value, out list)) { |
||||
list = new List<object>(length.Value); |
||||
} |
||||
} else { |
||||
if (!listTypeResolver.Value.TryGetListInstance(type, out list)) { |
||||
list = new List<object>(); |
||||
} |
||||
} |
||||
|
||||
return list; |
||||
} |
||||
|
||||
private void PopulateFixLengthList(IList<object> list, int length) |
||||
{ |
||||
for (var i = 0; i < length; ++i) { |
||||
list.Add(ReadValue()); |
||||
} |
||||
} |
||||
|
||||
private void PopulateVarList(IList<object> list) |
||||
{ |
||||
while (true) { |
||||
var tag = reader.Peek(); |
||||
if (!tag.HasValue) { |
||||
throw new EndOfStreamException(); |
||||
} |
||||
if (tag == 'Z') { |
||||
reader.ReadByte(); |
||||
break; |
||||
} |
||||
list.Add(ReadValue()); |
||||
} |
||||
} |
||||
|
||||
#endregion |
||||
|
||||
public object Reserved () |
||||
{ |
||||
reader.ReadByte(); |
||||
return ReadValue(); |
||||
} |
||||
|
||||
#region String |
||||
|
||||
public string ReadString() |
||||
{ |
||||
var tag = reader.Peek(); |
||||
|
||||
if (!tag.HasValue) { |
||||
throw new EndOfStreamException(); |
||||
} |
||||
|
||||
if (tag.Value < 0x20) { |
||||
return ReadShortString(); |
||||
} |
||||
|
||||
if (tag.Value >= 0x30 && tag.Value <= 0x33) { |
||||
return ReadMediumString(); |
||||
} |
||||
|
||||
if (tag.Value == 'R' || tag.Value == 'S') { |
||||
return ReadChunkedString(); |
||||
} |
||||
|
||||
throw new UnexpectedTagException(tag.Value, "string"); |
||||
} |
||||
|
||||
private string ReadShortString () |
||||
{ |
||||
var length = reader.ReadByte(); |
||||
return ReadStringWithLength(length); |
||||
} |
||||
|
||||
private string ReadMediumString () |
||||
{ |
||||
var b0 = reader.ReadByte (); |
||||
var b1 = reader.ReadByte (); |
||||
var length = ((b0 - 0x30) << 8) | b1; |
||||
return ReadStringWithLength(length); |
||||
} |
||||
|
||||
private string ReadStringWithLength (int length) |
||||
{ |
||||
var sb = new StringBuilder (length); |
||||
while (length-- > 0) { |
||||
sb.AppendCodepoint(reader.ReadUtf8Codepoint()); |
||||
} |
||||
return sb.ToString(); |
||||
} |
||||
|
||||
private string ReadChunkedString() |
||||
{ |
||||
var sb = new StringBuilder(); |
||||
var final = false; |
||||
|
||||
while (!final) { |
||||
var tag = reader.ReadByte(); |
||||
final = tag == 'S'; |
||||
var length = reader.ReadShort(); |
||||
while (length-- > 0) { |
||||
sb.AppendCodepoint(reader.ReadUtf8Codepoint()); |
||||
} |
||||
} |
||||
|
||||
return sb.ToString(); |
||||
} |
||||
|
||||
#endregion |
||||
|
||||
#region Binary |
||||
|
||||
public byte[] ReadBinary() |
||||
{ |
||||
var tag = reader.Peek(); |
||||
if (!tag.HasValue) { |
||||
throw new EndOfStreamException(); |
||||
} |
||||
|
||||
if (tag.Value >= 0x20 && tag.Value <= 0x2F) { |
||||
return ReadShortBinary(); |
||||
} |
||||
|
||||
if (tag.Value >= 0x34 && tag.Value <= 0x37) { |
||||
return ReadMediumBinary(); |
||||
} |
||||
|
||||
if (tag.Value == 0x41 || tag.Value == 0x42) { |
||||
return ReadChunkedBinary(); |
||||
} |
||||
|
||||
throw new UnexpectedTagException(tag.Value, "binary"); |
||||
} |
||||
|
||||
private byte[] ReadShortBinary () |
||||
{ |
||||
var length = reader.ReadByte(); |
||||
var data = new byte[length]; |
||||
reader.Read(data, length); |
||||
return data; |
||||
} |
||||
|
||||
private byte[] ReadMediumBinary() |
||||
{ |
||||
var b0 = reader.ReadByte(); |
||||
var b1 = reader.ReadByte(); |
||||
var length = ((b0 - 0x34) << 8) | b1; |
||||
var data = new byte[length]; |
||||
reader.Read(data, length); |
||||
return data; |
||||
} |
||||
|
||||
public byte[] ReadChunkedBinary() |
||||
{ |
||||
var data = new List<byte>(); |
||||
var final = false; |
||||
|
||||
while (!final) { |
||||
var tag = reader.ReadByte(); |
||||
final = tag == 'B'; |
||||
var length = reader.ReadShort(); |
||||
var buff = new byte[length]; |
||||
reader.Read(buff, length); |
||||
data.AddRange(buff); |
||||
} |
||||
|
||||
return data.ToArray(); |
||||
} |
||||
|
||||
#endregion Binary |
||||
|
||||
#region Integer |
||||
|
||||
public int ReadInteger() |
||||
{ |
||||
var tag = reader.Peek(); |
||||
|
||||
if (!tag.HasValue) { |
||||
throw new EndOfStreamException(); |
||||
} |
||||
|
||||
// Full-length integer encoding is 'I' b0 b1 b2 b3 - i.e. a full 32-bit integer in big-endian order. |
||||
if (tag == 0x49) { |
||||
return ReadIntegerFull(); |
||||
} |
||||
|
||||
// Ints between -16 and 47 are encoded as value + 0x90. |
||||
if (tag >= 0x80 && tag <= 0xBF) { |
||||
return ReadIntegerSingleByte(); |
||||
} |
||||
|
||||
// Ints between -2048 and 2047 can be encoded as two octets with the leading byte from 0xC0 to 0xCF. |
||||
if (tag >= 0xC0 && tag <= 0xCF) { |
||||
return ReadIntegerTwoBytes(); |
||||
} |
||||
|
||||
// Ints between -262144 and 262143 can be three bytes with the first from 0xD0 to 0xD7. |
||||
if (tag >= 0xD0 && tag <= 0xD7) { |
||||
return ReadIntegerThreeBytes(); |
||||
} |
||||
|
||||
throw new UnexpectedTagException(tag.Value, "integer"); |
||||
} |
||||
|
||||
private int ReadIntegerFull() |
||||
{ |
||||
reader.ReadByte(); // Discard tag. |
||||
byte b0 = reader.ReadByte(), |
||||
b1 = reader.ReadByte(), |
||||
b2 = reader.ReadByte(), |
||||
b3 = reader.ReadByte(); |
||||
|
||||
return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3; |
||||
} |
||||
|
||||
private int ReadIntegerSingleByte() |
||||
{ |
||||
return reader.ReadByte() - 0x90; |
||||
} |
||||
|
||||
private int ReadIntegerTwoBytes() |
||||
{ |
||||
byte b0 = reader.ReadByte(), |
||||
b1 = reader.ReadByte(); |
||||
|
||||
return ((b0 - 0xC8) << 8) | b1; |
||||
} |
||||
|
||||
private int ReadIntegerThreeBytes() |
||||
{ |
||||
byte b0 = reader.ReadByte(), |
||||
b1 = reader.ReadByte(), |
||||
b2 = reader.ReadByte(); |
||||
|
||||
return ((b0 - 0xD4) << 16) | (b1 << 8) | b2; |
||||
} |
||||
|
||||
#endregion Integer |
||||
|
||||
#region Class Definition |
||||
|
||||
public ClassDef ReadClassDefinition() |
||||
{ |
||||
var tag = reader.ReadByte(); |
||||
if (tag != 'C') { |
||||
throw new UnexpectedTagException(tag, "classdef"); |
||||
} |
||||
var name = ReadString(); |
||||
var fieldCount = ReadInteger(); |
||||
var fields = new string[fieldCount]; |
||||
for (var i = 0; i < fields.Length; ++i) { |
||||
fields[i] = ReadString(); |
||||
} |
||||
|
||||
var classDef = new ClassDef(name, fields); |
||||
|
||||
classDefs.Add(classDef); |
||||
|
||||
return classDef; |
||||
} |
||||
|
||||
#endregion Class Definition |
||||
|
||||
#region Double |
||||
|
||||
public double ReadDouble() |
||||
{ |
||||
var tag = reader.Peek(); |
||||
|
||||
if (!tag.HasValue) { |
||||
throw new EndOfStreamException(); |
||||
} |
||||
|
||||
if (tag == 0x44) { |
||||
return ReadFullDouble(); |
||||
} |
||||
|
||||
if (tag == 0x5B || tag == 0x5C) { |
||||
return ReadDoubleOneByte(); |
||||
} |
||||
|
||||
if (tag == 0x5D) { |
||||
return ReadDoubleTwoBytes(); |
||||
} |
||||
|
||||
if (tag == 0x5E) { |
||||
return ReadDoubleThreeBytes(); |
||||
} |
||||
|
||||
if (tag == 0x5F) { |
||||
return ReadDoubleFourBytes(); |
||||
} |
||||
|
||||
throw new UnexpectedTagException(tag.Value, "double"); |
||||
} |
||||
|
||||
private double ReadFullDouble() |
||||
{ |
||||
var data = new byte[9]; // 9 bytes: tag + IEEE 8-byte double |
||||
reader.Read(data, data.Length); |
||||
return BitConverter.ToDouble(data, 1); |
||||
} |
||||
|
||||
private double ReadDoubleOneByte() |
||||
{ |
||||
// 0x5B encodes the double value 0.0, and 0x5C encodes 1.0. |
||||
return reader.ReadByte() - 0x5B; |
||||
} |
||||
|
||||
private double ReadDoubleTwoBytes() |
||||
{ |
||||
// Doubles representing integral values between -128.0 and 127.0 are |
||||
// encoded as single bytes. Java bytes are signed, .NET bytes aren't, |
||||
// so we have to cast it first. |
||||
reader.ReadByte(); |
||||
return (sbyte) reader.ReadByte(); |
||||
} |
||||
|
||||
private double ReadDoubleThreeBytes() |
||||
{ |
||||
// Doubles representing integral values between -32768.0 and 32767.0 are |
||||
// encoded as two-byte integers. |
||||
reader.ReadByte(); |
||||
return reader.ReadShort(); |
||||
} |
||||
|
||||
private double ReadDoubleFourBytes() |
||||
{ |
||||
// Doubles that can be represented as singles are thusly encoded. |
||||
var data = new byte[5]; |
||||
reader.Read(data, data.Length); |
||||
return BitConverter.ToSingle(data, 0); |
||||
} |
||||
|
||||
#endregion Double |
||||
|
||||
public bool ReadBoolean() |
||||
{ |
||||
var tag = reader.ReadByte(); |
||||
|
||||
switch (tag) { |
||||
case 0x46: return false; |
||||
case 0x54: return true; |
||||
} |
||||
|
||||
throw new UnexpectedTagException(tag, "boolean"); |
||||
} |
||||
|
||||
#region Date |
||||
|
||||
public DateTime ReadDate() |
||||
{ |
||||
var tag = reader.Peek(); |
||||
|
||||
if (!tag.HasValue) { |
||||
throw new EndOfStreamException(); |
||||
} |
||||
|
||||
if (tag == 0x4A) { |
||||
return ReadDateInMillis(); |
||||
} |
||||
|
||||
if (tag == 0x4B) { |
||||
return ReadDateInMinutes(); |
||||
} |
||||
|
||||
throw new UnexpectedTagException(tag.Value, "date"); |
||||
} |
||||
|
||||
private DateTime ReadDateInMillis() |
||||
{ |
||||
var data = new byte[9]; |
||||
reader.Read(data, data.Length); |
||||
var millis = LongFromBytes(data, 1); |
||||
return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(millis); |
||||
} |
||||
|
||||
private DateTime ReadDateInMinutes() |
||||
{ |
||||
var data = new byte[5]; |
||||
reader.Read(data, data.Length); |
||||
var minutes = IntFromBytes(data, 1); |
||||
return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMinutes(minutes); |
||||
} |
||||
|
||||
#endregion Date |
||||
|
||||
#region Long |
||||
|
||||
public long ReadLong() |
||||
{ |
||||
var tag = reader.Peek(); |
||||
|
||||
if (!tag.HasValue) { |
||||
throw new EndOfStreamException(); |
||||
} |
||||
|
||||
if (tag == 0x4C) { |
||||
return ReadLongFull(); |
||||
} |
||||
|
||||
if (tag >= 0xD8 && tag <= 0xEF) { |
||||
return ReadLongOneByte(); |
||||
} |
||||
|
||||
if (tag >= 0xF0 && tag <= 0xFF) { |
||||
return ReadLongTwoBytes(); |
||||
} |
||||
|
||||
if (tag >= 0x38 && tag <= 0x3F) { |
||||
return ReadLongThreeBytes(); |
||||
} |
||||
|
||||
if (tag == 0x59) { |
||||
return ReadLongFourBytes(); |
||||
} |
||||
|
||||
throw new UnexpectedTagException(tag.Value, "long"); |
||||
} |
||||
|
||||
private long ReadLongFull() |
||||
{ |
||||
var data = new byte[9]; |
||||
reader.Read(data, data.Length); |
||||
return LongFromBytes(data, 1); |
||||
} |
||||
|
||||
private long ReadLongOneByte() |
||||
{ |
||||
return reader.ReadByte() - 0xE0; |
||||
} |
||||
|
||||
private long ReadLongTwoBytes() |
||||
{ |
||||
byte b0 = reader.ReadByte(), |
||||
b1 = reader.ReadByte(); |
||||
|
||||
return ((b0 - 0xF8) << 8) | b1; |
||||
} |
||||
|
||||
private long ReadLongThreeBytes() |
||||
{ |
||||
byte b0 = reader.ReadByte(), |
||||
b1 = reader.ReadByte(), |
||||
b2 = reader.ReadByte(); |
||||
|
||||
return ((b0 - 0x3C) << 16) | (b1 << 8) | b2; |
||||
} |
||||
|
||||
private long ReadLongFourBytes() |
||||
{ |
||||
var data = new byte[5]; |
||||
reader.Read(data, data.Length); |
||||
return IntFromBytes(data, 1); |
||||
} |
||||
|
||||
#endregion Long |
||||
|
||||
#region Dictionary/Map |
||||
|
||||
public IDictionary<object, object> ReadMap() |
||||
{ |
||||
var tag = reader.Peek(); |
||||
|
||||
if (!tag.HasValue) { |
||||
throw new EndOfStreamException(); |
||||
} |
||||
|
||||
if (tag == 'H') { |
||||
return ReadUntypedMap(); |
||||
} |
||||
|
||||
if (tag == 'M') { |
||||
return ReadTypedMap(); |
||||
} |
||||
|
||||
throw new UnexpectedTagException(tag.Value, "map"); |
||||
} |
||||
|
||||
private IDictionary<object, object> ReadUntypedMap() |
||||
{ |
||||
reader.ReadByte(); |
||||
return ReadMapCore(); |
||||
} |
||||
|
||||
private IDictionary<object, object> ReadTypedMap() |
||||
{ |
||||
reader.ReadByte(); |
||||
var typeName = ReadTypeName(); |
||||
return ReadMapCore(typeName); |
||||
} |
||||
|
||||
private IDictionary<object, object> ReadMapCore(string type = null) |
||||
{ |
||||
IDictionary<object, object> dictionary; |
||||
if (type == null || !dictTypeResolver.Value.TryGetInstance("", out dictionary)) { |
||||
dictionary = new Dictionary<object, object>(); |
||||
} |
||||
|
||||
objectRefs.Add(dictionary); |
||||
|
||||
while (true) { |
||||
var tag = reader.Peek(); |
||||
|
||||
if (!tag.HasValue) { |
||||
throw new EndOfStreamException(); |
||||
} |
||||
if (tag == 'Z') { |
||||
break; |
||||
} |
||||
|
||||
var key = ReadValue(); |
||||
var value = ReadValue(); |
||||
dictionary.Add(key, value); |
||||
} |
||||
|
||||
return dictionary; |
||||
} |
||||
|
||||
#endregion |
||||
|
||||
#region Object |
||||
|
||||
public object ReadObject() |
||||
{ |
||||
var tag = reader.Peek(); |
||||
|
||||
if (!tag.HasValue) { |
||||
throw new EndOfStreamException(); |
||||
} |
||||
|
||||
if (tag == 'O') { |
||||
return ReadObjectFull(); |
||||
} |
||||
|
||||
if (tag >= 0x60 && tag < 0x70) { |
||||
return ReadObjectCompact(); |
||||
} |
||||
|
||||
throw new UnexpectedTagException(tag.Value, "object"); |
||||
} |
||||
|
||||
private object ReadObjectFull() |
||||
{ |
||||
reader.ReadByte(); |
||||
var classDefId = ReadInteger(); |
||||
var classDef = classDefs.Get(classDefId); |
||||
return ReadObjectCore(classDef); |
||||
} |
||||
|
||||
private object ReadObjectCompact() |
||||
{ |
||||
var classDefId = reader.ReadByte() - 0x60; |
||||
var classDef = classDefs.Get(classDefId); |
||||
return ReadObjectCore(classDef); |
||||
} |
||||
|
||||
private object ReadObjectCore(ClassDef classDef) |
||||
{ |
||||
// XXX: This needs a better implementation - maybe, you know, constructing |
||||
// the requested type? |
||||
var builder = HessianObject.Builder.New(classDef.Name); |
||||
objectRefs.Add(builder.Object); |
||||
|
||||
foreach (var field in classDef.Fields) { |
||||
builder.Add(field, ReadValue()); |
||||
} |
||||
|
||||
return builder.Create(); |
||||
} |
||||
|
||||
#endregion |
||||
|
||||
public object ReadNull() |
||||
{ |
||||
reader.ReadByte(); |
||||
return null; |
||||
} |
||||
|
||||
public object ReadRef() |
||||
{ |
||||
var tag = reader.Peek(); |
||||
if (!tag.HasValue) { |
||||
throw new EndOfStreamException(); |
||||
} |
||||
if (tag != 0x51) { |
||||
throw new UnexpectedTagException(tag.Value, "ref"); |
||||
} |
||||
return objectRefs.Get(ReadInteger()); |
||||
} |
||||
|
||||
private static int IntFromBytes(byte[] buffer, int offset) |
||||
{ |
||||
return (buffer[offset + 0] << 0x18) |
||||
| (buffer[offset + 1] << 0x10) |
||||
| (buffer[offset + 2] << 0x08) |
||||
| (buffer[offset + 3] << 0x00); |
||||
} |
||||
|
||||
private static long LongFromBytes(byte[] buffer, int offset) |
||||
{ |
||||
return (buffer[offset + 0] << 0x38) |
||||
| (buffer[offset + 1] << 0x30) |
||||
| (buffer[offset + 2] << 0x28) |
||||
| (buffer[offset + 3] << 0x20) |
||||
| (buffer[offset + 4] << 0x18) |
||||
| (buffer[offset + 5] << 0x10) |
||||
| (buffer[offset + 6] << 0x08) |
||||
| (buffer[offset + 7] << 0x00); |
||||
} |
||||
} |
||||
} |
||||
|
||||
@ -0,0 +1,24 @@ |
||||
using System; |
||||
using System.IO; |
||||
using System.Text; |
||||
|
||||
namespace Hessian |
||||
{ |
||||
class MainClass |
||||
{ |
||||
public static void Main (string[] args) |
||||
{ |
||||
Console.WriteLine ("Hello World!"); |
||||
var bytes = Encoding.UTF8.GetBytes("Hessian"); |
||||
var ms = new MemoryStream(); |
||||
ms.WriteByte((byte)"Hessian".Length); |
||||
ms.Write (bytes, 0, bytes.Length); |
||||
ms.Position = 0; |
||||
|
||||
var ds = new Deserializer(ms); |
||||
|
||||
var actual = ds.ReadValue(); |
||||
Console.WriteLine(actual); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,142 @@ |
||||
using System; |
||||
using System.IO; |
||||
|
||||
namespace Hessian |
||||
{ |
||||
public class PeekStream : Stream |
||||
{ |
||||
private Stream inner; |
||||
private byte? peek; |
||||
|
||||
public PeekStream(Stream inner) |
||||
{ |
||||
if (inner == null) { |
||||
throw new ArgumentNullException("inner"); |
||||
} |
||||
|
||||
this.inner = inner; |
||||
this.peek = null; |
||||
} |
||||
|
||||
public override bool CanRead { |
||||
get { |
||||
return inner.CanRead; |
||||
} |
||||
} |
||||
|
||||
public override bool CanSeek { |
||||
get { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
public override bool CanWrite { |
||||
get { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
public override long Length { |
||||
get { |
||||
return inner.Length; |
||||
} |
||||
} |
||||
|
||||
public override long Position { |
||||
get { |
||||
return inner.Position - (peek.HasValue ? 1 : 0); |
||||
} |
||||
set { |
||||
throw new NotSupportedException("Seeking not supported."); |
||||
} |
||||
} |
||||
|
||||
public byte? Peek () |
||||
{ |
||||
if (!peek.HasValue) { |
||||
var b = inner.ReadByte(); |
||||
|
||||
if (b == -1) { |
||||
return null; |
||||
} |
||||
|
||||
peek = (byte) b; |
||||
} |
||||
|
||||
return peek; |
||||
} |
||||
|
||||
public override int ReadByte () |
||||
{ |
||||
if (peek.HasValue) { |
||||
var val = peek.Value; |
||||
peek = null; |
||||
return val; |
||||
} |
||||
|
||||
return inner.ReadByte(); |
||||
} |
||||
|
||||
public override int Read (byte[] buffer, int offset, int count) |
||||
{ |
||||
Conditions.CheckNotNull(buffer, "buffer"); |
||||
Conditions.CheckGreaterOrEqual(offset, 0, "offset"); |
||||
Conditions.CheckLess(offset, buffer.Length, "offset"); |
||||
Conditions.CheckGreaterOrEqual(count, 0, "count"); |
||||
Conditions.CheckArgument( |
||||
offset + count < buffer.Length, |
||||
"Buffer is not big enough to contain the requested amount of data at the given offset."); |
||||
|
||||
if (count == 0) { |
||||
return 0; |
||||
} |
||||
|
||||
var bytesToRead = count; |
||||
|
||||
if (peek.HasValue) { |
||||
buffer[offset++] = peek.Value; |
||||
peek = null; |
||||
--bytesToRead; |
||||
} |
||||
|
||||
int bytesRead; |
||||
while (bytesToRead > 0 && (bytesRead = inner.Read (buffer, offset, bytesToRead)) != 0) { |
||||
offset += bytesRead; |
||||
bytesToRead -= bytesRead; |
||||
} |
||||
|
||||
return count - bytesToRead; |
||||
} |
||||
|
||||
public override void Write (byte[] buffer, int offset, int count) |
||||
{ |
||||
throw new NotSupportedException("Writes not supported."); |
||||
} |
||||
|
||||
public override void SetLength (long value) |
||||
{ |
||||
throw new NotSupportedException("Seeking not supported."); |
||||
} |
||||
|
||||
public override long Seek (long offset, SeekOrigin origin) |
||||
{ |
||||
throw new NotSupportedException("Seeking not supported."); |
||||
} |
||||
|
||||
public override void Flush () |
||||
{ |
||||
throw new NotSupportedException("Writes not supported."); |
||||
} |
||||
|
||||
protected override void Dispose (bool disposing) |
||||
{ |
||||
if (inner != null) { |
||||
inner.Dispose (); |
||||
inner = null; |
||||
} |
||||
|
||||
base.Dispose (disposing); |
||||
} |
||||
} |
||||
} |
||||
|
||||
@ -0,0 +1,39 @@ |
||||
using System; |
||||
using System.IO; |
||||
using System.Text; |
||||
|
||||
namespace Hessian |
||||
{ |
||||
public static class StringBuilderExtensions |
||||
{ |
||||
public static StringBuilder AppendCodepoint(this StringBuilder sb, uint codepoint) |
||||
{ |
||||
if (codepoint < 0x10000) { |
||||
return sb.Append((char)codepoint); |
||||
} |
||||
|
||||
var n = codepoint - 0x10000; |
||||
var high = (char)((n >> 10) + 0xD800); |
||||
var low = (char)((n & 0x3FF) + 0xDC00); |
||||
|
||||
AssertValidSurrogates(high, low); |
||||
|
||||
return sb |
||||
.Append (high) |
||||
.Append (low); |
||||
} |
||||
|
||||
[System.Diagnostics.Conditional("DEBUG")] |
||||
private static void AssertValidSurrogates (char high, char low) |
||||
{ |
||||
if (!Char.IsHighSurrogate (high)) { |
||||
throw new InvalidDataException ("Invalid high surrogate"); |
||||
} |
||||
|
||||
if (!Char.IsLowSurrogate (low)) { |
||||
throw new InvalidDataException ("Invalid low surrogate"); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
@ -0,0 +1,163 @@ |
||||
using System; |
||||
using System.IO; |
||||
|
||||
namespace Hessian |
||||
{ |
||||
public class ValueReader |
||||
{ |
||||
private byte[] buffer = new byte[8]; |
||||
private PeekStream stream; |
||||
|
||||
public ValueReader (Stream stream) |
||||
{ |
||||
this.stream = stream as PeekStream ?? new PeekStream(stream); |
||||
} |
||||
|
||||
public byte? Peek () |
||||
{ |
||||
return stream.Peek (); |
||||
} |
||||
|
||||
public short ReadShort () |
||||
{ |
||||
Read (buffer, 0, 2); |
||||
return BitConverter.ToInt16(buffer, 0); |
||||
} |
||||
|
||||
public int ReadInt() |
||||
{ |
||||
Read (buffer, 0, 4); |
||||
return BitConverter.ToInt32(buffer, 0); |
||||
} |
||||
|
||||
public uint ReadUtf8Codepoint () |
||||
{ |
||||
const uint replacementChar = 0xFFFD; |
||||
|
||||
byte b0, b1, b2, b3; |
||||
b0 = ReadByte (); |
||||
|
||||
if (b0 < 0x80) { |
||||
return b0; |
||||
} |
||||
|
||||
if (b0 < 0xC2) { |
||||
return replacementChar; |
||||
} |
||||
|
||||
if (b0 < 0xE0) { |
||||
b1 = ReadByte (); |
||||
|
||||
if ((b1 ^ 0x80) >= 0x40) { |
||||
return replacementChar; |
||||
} |
||||
|
||||
return (b1 & 0x3Fu) | ((b0 & 0x1Fu) << 6); |
||||
} |
||||
|
||||
if (b0 < 0xF0) { |
||||
b1 = ReadByte (); |
||||
b2 = ReadByte (); |
||||
|
||||
// Valid range: E0 A0..BF 80..BF |
||||
if (b0 == 0xE0 && (b1 ^ 0xA0) >= 0x20) { |
||||
return replacementChar; |
||||
} |
||||
|
||||
// Valid range: ED 80..9F 80..BF |
||||
if (b0 == 0xED && (b1 ^ 0x80) >= 0x20) { |
||||
return replacementChar; |
||||
} |
||||
|
||||
// Valid range: E1..EC 80..BF 80..BF |
||||
if ((b1 ^ 0x80) >= 0x40 || (b2 ^ 0x80) >= 0x40) { |
||||
return replacementChar; |
||||
} |
||||
|
||||
return (b2 & 0x3Fu) |
||||
| ((b1 & 0x3Fu) << 6) |
||||
| ((b0 & 0x0Fu) << 12); |
||||
} |
||||
|
||||
if (b0 < 0xF1) { |
||||
b1 = ReadByte(); |
||||
|
||||
if ((b1 ^ 0x90) < 0x30) { |
||||
return replacementChar; |
||||
} |
||||
|
||||
b2 = ReadByte(); |
||||
b3 = ReadByte(); |
||||
|
||||
if ((b2 & 0xC0) != 0x80 || (b3 & 0xC0) != 0x80) { |
||||
return replacementChar; |
||||
} |
||||
|
||||
return (b3 & 0x3Fu) |
||||
| ((b2 & 0x3Fu) << 6) |
||||
| ((b1 & 0x3Fu) << 12) |
||||
| ((b0 & 0x07u) << 18); |
||||
} |
||||
|
||||
if (b0 < 0xF4) { |
||||
b1 = ReadByte (); |
||||
b2 = ReadByte (); |
||||
b3 = ReadByte (); |
||||
|
||||
// Valid range: F1..F3 80..BF 80..BF 80..BF |
||||
if ((b1 & 0xC0) != 0x80 || (b2 & 0xC0) != 0x80 || (b3 & 0xC0) != 0x80) |
||||
{ |
||||
return replacementChar; |
||||
} |
||||
|
||||
return (b3 & 0x3Fu) |
||||
| ((b2 & 0x3Fu) << 6) |
||||
| ((b1 & 0x3Fu) << 12) |
||||
| ((b0 & 0x07u) << 18); |
||||
} |
||||
|
||||
if (b0 < 0xF5) { |
||||
b1 = ReadByte (); |
||||
|
||||
// Valid range: F4 80..8F 80..BF 80..BF |
||||
if ((b1 ^ 0x80) >= 0x10) { |
||||
return replacementChar; |
||||
} |
||||
|
||||
b2 = ReadByte(); |
||||
b3 = ReadByte(); |
||||
|
||||
if ((b2 & 0xC0) != 0x80 || (b3 & 0xC0) != 0x80) |
||||
{ |
||||
return replacementChar; |
||||
} |
||||
|
||||
return (b3 & 0x3Fu) |
||||
| ((b2 & 0x3Fu) << 6) |
||||
| ((b1 & 0x3Fu) << 12) |
||||
| ((b0 & 0x07u) << 18); |
||||
} |
||||
|
||||
return replacementChar; |
||||
} |
||||
|
||||
public byte ReadByte() |
||||
{ |
||||
var b = stream.ReadByte(); |
||||
if (b == -1) throw new EndOfStreamException(); |
||||
return (byte)b; |
||||
} |
||||
|
||||
public void Read(byte[] buffer, int count) |
||||
{ |
||||
Read (buffer, 0, count); |
||||
} |
||||
|
||||
private void Read(byte[] buffer, int offset, int count) |
||||
{ |
||||
var bytesRead = stream.Read (buffer, offset, count); |
||||
if (bytesRead != count) throw new EndOfStreamException(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
Loading…
Reference in new issue