public Task <MethodExecutionState> ReadStateAsync( ServiceId serviceId, PersistedMethodId methodId, CancellationToken ct) { MethodExecutionState executionState; string serializedFlowContext; string serializedContinuation; string continuationStateFormat; byte[] continuationStateData; lock (_entryMap) { if (!_entryMap.TryGetValue(methodId.IntentId, out var entry)) { throw new StateNotFoundException(serviceId, methodId); } executionState = new MethodExecutionState { Service = serviceId, Method = methodId, Caller = entry.TryGetValue("Caller", out var callerObj) ? callerObj as CallerDescriptor : null, MethodState = new SerializedValueContainer((string)entry["State"], _serializer) }; executionState.Method.ETag = entry.ETag; serializedFlowContext = entry.TryGetValue("FlowContext", out var flowContextObj) ? flowContextObj as string : null; serializedContinuation = entry.TryGetValue("Continuation", out var continuationObj) ? continuationObj as string : null; continuationStateFormat = entry.TryGetValue("Continuation:Format", out var continuationStateFormatObj) ? continuationStateFormatObj as string : null; continuationStateData = entry.TryGetValue("Continuation:State", out var continuationStateDataObj) ? continuationStateDataObj as byte[] : null; } if (!string.IsNullOrEmpty(serializedFlowContext)) { executionState.FlowContext = _serializer.Deserialize <Dictionary <string, string> >(serializedFlowContext); } if (!string.IsNullOrEmpty(serializedContinuation)) { executionState.Continuation = _serializer.Deserialize <ContinuationDescriptor>(serializedContinuation); } if (continuationStateData?.Length > 0) { executionState.ContinuationState = new SerializedMethodContinuationState { Format = continuationStateFormat, State = continuationStateData }; } return(Task.FromResult(executionState)); }
public void Read(IObjectReconstructor reconstructor, ISerializer serializer) { var stateStack = new Stack <State>(); State state = null; while (_jsonReader.Read()) { switch (_jsonReader.TokenType) { case JsonToken.StartObject: { if (state?.Info.Name != null && reconstructor.GetExpectedValueType(state.Info.Name) == typeof(IValueContainer)) { var content = new StringBuilder(); var textWriter = new StringWriter(content); var jsonWriter = new JsonTextWriter(textWriter); jsonWriter.WriteToken(_jsonReader, writeChildren: true); var serializedContainer = new SerializedValueContainer( content.ToString(), serializer); reconstructor.OnValueStart(state.Info); reconstructor.OnValue(serializedContainer); reconstructor.OnValueEnd(); if (stateStack.Count > 0) { state = stateStack.Pop(); if (state.Info.IsCollection) { stateStack.Push(state); state = new State(); } } else { state = null; } } else { if (state == null) { state = new State(); } state.HasMetadata = true; } } break; case JsonToken.EndObject: { if (state.ValueStarted && !state.ValueEnded) { reconstructor.OnValueEnd(); state.ValueEnded = true; } else if (!state.ValueStarted && (state.Info.ReferenceId != null || state.Info.SpecialId != null)) { reconstructor.OnValueStart(state.Info); reconstructor.OnValueEnd(); } if (stateStack.Count > 0) { state = stateStack.Pop(); if (state.Info.IsCollection) { stateStack.Push(state); state = new State(); } } else { state = null; } } break; case JsonToken.PropertyName: { var name = (string)_jsonReader.Value; if (name[0] == '$') { if (name == "$id") { ReadObjectId(ref state.Info); } else if (name == "$itemCount") { ReadItemCount(ref state.Info); } else if (name == "$type") { state.Info.Type = ReadTypeInfo(); } else if (name == "$itemType") { state.Info.IsCollection = true; state.Info.ItemType = ReadTypeInfo(); } else if (name == "$items") { state.Info.IsCollection = true; if (!state.ValueStarted) { reconstructor.OnValueStart(state.Info); state.ValueStarted = true; } } else if (name == "$value") { if (!state.ValueStarted) { reconstructor.OnValueStart(state.Info); state.ValueStarted = true; } } else if (name == "$typeIsValue") { if (ReadTypeIsValue()) { var value = state.Info.Type; if (!state.ValueStarted) { state.Info.Type = TypeSerializationInfo.Self; reconstructor.OnValueStart(state.Info); state.ValueStarted = true; } reconstructor.OnValue(value); } } else { throw new InvalidOperationException($"Unknown directive '{name}'"); } } else { if (!state.ValueStarted) { reconstructor.OnValueStart(state.Info); state.ValueStarted = true; } stateStack.Push(state); state = new State { Info = new ValueInfo { Name = name } }; } } break; case JsonToken.StartArray: { state.Info.IsCollection = true; if (!state.ValueStarted) { reconstructor.OnValueStart(state.Info); state.ValueStarted = true; } stateStack.Push(state); state = new State(); } break; case JsonToken.EndArray: { state = stateStack.Pop(); if (!state.HasMetadata && state.ValueStarted) { reconstructor.OnValueEnd(); state.ValueEnded = true; } } break; case JsonToken.Boolean: case JsonToken.Bytes: case JsonToken.Date: case JsonToken.Float: case JsonToken.Integer: case JsonToken.Null: case JsonToken.String: { if (!state.HasMetadata) { reconstructor.OnValueStart(state.Info); } reconstructor.OnValue(_jsonReader.Value); if (!state.HasMetadata) { reconstructor.OnValueEnd(); if (state.Info.Name != null) { state = stateStack.Pop(); } } } break; default: throw new InvalidOperationException( $"Unexpected JSON token '{_jsonReader.TokenType}'."); } } }
public async Task HandleAsync(PathString basePath, HttpContext context, CancellationToken ct) { var isQueryRequest = context.Request.Method == "GET"; var isCommandRequest = context.Request.Method == "POST"; if (!isQueryRequest && !isCommandRequest) { await ReplyWithTextError(context.Response, 405, "Only GET and POST verbs are allowed"); return; } var headers = context.Response.Headers; headers.Add(DasyncHttpHeaders.PoweredBy, "D-ASYNC"); var basePathSegmentCount = basePath.Value.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries).Length; var pathSegments = context.Request.Path.Value.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries).Skip(basePathSegmentCount).ToArray(); if (pathSegments.Length == 0) { await ReplyWithTextError(context.Response, 404, "Empty request URL"); return; } var serviceId = new ServiceId { Name = pathSegments[0] }; if (!_serviceResolver.TryResolve(serviceId, out var serviceReference)) { await ReplyWithTextError(context.Response, 404, $"Service '{serviceId.Name}' is not registered"); return; } if (serviceReference.Definition.Type == ServiceType.External) { await ReplyWithTextError(context.Response, 404, $"Cannot invoke external service '{serviceReference.Definition.Name}' on your behalf"); return; } if (pathSegments.Length == 1) { await ReplyWithTextError(context.Response, 404, "The request URL does not contain a service method"); return; } var methodId = new MethodId { Name = pathSegments[1] }; string methodIntentId = null; if (pathSegments.Length == 3) { methodIntentId = pathSegments[2]; if (isQueryRequest) { await HandleResultPoll(context, serviceReference.Id, methodId, methodIntentId); return; } } if (pathSegments.Length > 3) { await ReplyWithTextError(context.Response, 404, "The request URL contains extra segments"); return; } var contentType = context.Request.GetContentType(); ISerializer serializer; try { serializer = GetSerializer(contentType, isQueryRequest); } catch (ArgumentException) { await ReplyWithTextError(context.Response, 406, $"The Content-Type '{contentType.MediaType}' is not supported"); return; } if (!_methodResolver.TryResolve(serviceReference.Definition, methodId, out var methodReference)) { await ReplyWithTextError(context.Response, 404, $"The service '{serviceReference.Definition.Name}' does not have method '{methodId.Name}'"); return; } if (isQueryRequest && !methodReference.Definition.IsQuery) { await ReplyWithTextError(context.Response, 404, $"The method '{methodId.Name}' of service '{serviceReference.Definition.Name}' is a command, but not a query, thus must be invoked with the POST verb"); return; } MethodInvocationData invokeData = null; MethodContinuationData continueData = null; IValueContainer parametersContainer; bool compressResponse = false; bool respondWithEnvelope = false; if (isQueryRequest) { var inputObject = new JObject(); foreach (var kvPair in context.Request.Query) { if (kvPair.Value.Count == 0) { continue; } var parameterName = kvPair.Key; if (kvPair.Value.Count == 1) { var parameterValue = kvPair.Value[0]; inputObject.Add(parameterName, parameterValue); } else { var values = new JArray(); foreach (var value in kvPair.Value) { values.Add(value); } inputObject.Add(parameterName, values); } } var parametersJson = inputObject.ToString(); parametersContainer = new SerializedValueContainer(parametersJson, _jsonSerializer); invokeData = new MethodInvocationData { Service = serviceId, Method = methodId, Parameters = parametersContainer, Caller = GetCaller(context.Request.Headers) }; } else { var payloadStream = context.Request.Body; var encoding = context.Request.GetContentEncoding(); if (!string.IsNullOrEmpty(encoding)) { if ("gzip".Equals(encoding, StringComparison.OrdinalIgnoreCase)) { compressResponse = true; payloadStream = new GZipStream(payloadStream, CompressionMode.Decompress, leaveOpen: true); } else if ("deflate".Equals(encoding, StringComparison.OrdinalIgnoreCase)) { payloadStream = new DeflateStream(payloadStream, CompressionMode.Decompress, leaveOpen: true); } else { await ReplyWithTextError(context.Response, 406, $"The Content-Encoding '{encoding}' is not supported"); return; } } var envelopeType = context.Request.Headers.GetValue(DasyncHttpHeaders.Envelope); if (string.IsNullOrEmpty(envelopeType)) { var payload = await payloadStream.ToBytesAsync(); parametersContainer = new SerializedValueContainer(payload, serializer); if (string.IsNullOrEmpty(methodIntentId)) { invokeData = new MethodInvocationData { Service = serviceId, Method = methodId, Parameters = parametersContainer, Caller = GetCaller(context.Request.Headers) }; } else { continueData = new MethodContinuationData { Service = serviceId, Method = methodId.CopyTo(new PersistedMethodId()), Result = parametersContainer, Caller = GetCaller(context.Request.Headers) }; continueData.Method.IntentId = methodIntentId; // TODO: get ETag from the query string } } else if (envelopeType.Equals("invoke", StringComparison.OrdinalIgnoreCase)) { respondWithEnvelope = true; invokeData = await DeserializeAsync <MethodInvocationData>(serializer, payloadStream); } else if (envelopeType.Equals("continue", StringComparison.OrdinalIgnoreCase)) { respondWithEnvelope = true; continueData = await DeserializeAsync <MethodContinuationData>(serializer, payloadStream); } else { await ReplyWithTextError(context.Response, 406, $"Unknown envelope type '{envelopeType}'"); return; } } var intentId = context.Request.Headers.GetValue(DasyncHttpHeaders.IntentId) ?? _idGenerator.NewId(); var externalRequestId = context.Request.Headers.GetValue(DasyncHttpHeaders.RequestId); var externalCorrelationId = context.Request.Headers.GetValue(DasyncHttpHeaders.CorrelationId); var isRetry = context.Request.Headers.IsRetry(); var rfc7240Preferences = context.Request.Headers.GetRFC7240Preferences(); var isHttpRequestBlockingExecution = !(rfc7240Preferences.RespondAsync == true); var waitTime = rfc7240Preferences.Wait; if (waitTime > MaxLongPollTime) { waitTime = MaxLongPollTime; } var waitForResult = isHttpRequestBlockingExecution || waitTime > TimeSpan.Zero; var communicatorMessage = new HttpCommunicatorMessage { IsRetry = isRetry, RequestId = externalRequestId, WaitForResult = waitForResult }; if (invokeData != null) { if (invokeData.IntentId == null) { invokeData.IntentId = intentId; } var invokeTask = _localTransitionRunner.RunAsync(invokeData, communicatorMessage); if (isHttpRequestBlockingExecution) { var invocationResult = await invokeTask; if (invocationResult.Outcome == InvocationOutcome.Complete) { await RespondWithMethodResult(context.Response, invocationResult.Result, serializer, respondWithEnvelope, compressResponse); return; } if (waitForResult) { var taskResult = await TryWaitForResultAsync( serviceReference.Id, methodId, intentId, waitTime); if (taskResult != null) { await RespondWithMethodResult(context.Response, taskResult, serializer, respondWithEnvelope, compressResponse); return; } } } else { // TODO: continue 'invokeTask' and handle exceptions in background } communicatorMessage.WaitForResult = false; var location = string.Concat(context.Request.Path, "/", intentId); context.Response.Headers.Add("Location", location); context.Response.Headers.Add(DasyncHttpHeaders.IntentId, intentId); context.Response.StatusCode = DasyncHttpCodes.Scheduled; } else if (continueData != null) { if (continueData.IntentId == null) { continueData.IntentId = intentId; } var continueTask = _localTransitionRunner.ContinueAsync(continueData, communicatorMessage); // TODO: continue 'continueTask' in backgraound to handle exceptions context.Response.Headers.Add("Location", context.Request.Path.ToString()); context.Response.Headers.Add(DasyncHttpHeaders.IntentId, continueData.Method.IntentId); context.Response.StatusCode = DasyncHttpCodes.Scheduled; } }