/// <summary> /// Call this method to handle requests on the server, sent by the client /// </summary> public async Task HandleRequestAsync(HttpContext context, Action <RemoteOrchestrator> action, CancellationToken cancellationToken) { var httpRequest = context.Request; var httpResponse = context.Response; var streamArray = GetBody(httpRequest); var serAndsizeString = string.Empty; var converter = string.Empty; // Get the serialization and batch size format if (TryGetHeaderValue(context.Request.Headers, "dotmim-sync-serialization-format", out var vs)) { serAndsizeString = vs.ToLowerInvariant(); } // Get the serialization and batch size format if (TryGetHeaderValue(context.Request.Headers, "dotmim-sync-converter", out var cs)) { converter = cs.ToLowerInvariant(); } if (!TryGetHeaderValue(context.Request.Headers, "dotmim-sync-session-id", out var sessionId)) { throw new HttpHeaderMissingExceptiopn("dotmim-sync-session-id"); } if (!TryGetHeaderValue(context.Request.Headers, "dotmim-sync-step", out string iStep)) { throw new HttpHeaderMissingExceptiopn("dotmim-sync-step"); } var step = (HttpStep)Convert.ToInt32(iStep); try { // Create a web server remote orchestrator based on cache values if (this.WebServerOrchestrator == null) { this.WebServerOrchestrator = this.WebServerProperties.CreateWebServerOrchestrator(); } // try get schema from cache if (this.Cache.TryGetValue <SyncSet>(WebServerProperties.Key, out var cachedSchema)) { this.WebServerOrchestrator.Schema = cachedSchema; } // try get session cache from current sessionId if (!this.Cache.TryGetValue <SessionCache>(sessionId, out var sessionCache)) { sessionCache = new SessionCache(); } // action from user if available action?.Invoke(this.WebServerOrchestrator); // Get the serializer and batchsize (var clientBatchSize, var clientSerializerFactory) = GetClientSerializer(serAndsizeString, this.WebServerOrchestrator); // Get converter used by client // Can be null var clientConverter = GetClientConverter(converter, this.WebServerOrchestrator); this.WebServerOrchestrator.ClientConverter = clientConverter; byte[] binaryData = null; switch (step) { case HttpStep.EnsureScopes: var m1 = clientSerializerFactory.GetSerializer <HttpMessageEnsureScopesRequest>().Deserialize(streamArray); var s1 = await this.WebServerOrchestrator.EnsureScopeAsync(m1, sessionCache, cancellationToken).ConfigureAwait(false); binaryData = clientSerializerFactory.GetSerializer <HttpMessageEnsureScopesResponse>().Serialize(s1); await this.WebServerOrchestrator.Provider.InterceptAsync(new HttpMessageEnsureScopesResponseArgs(binaryData)).ConfigureAwait(false); break; case HttpStep.SendChanges: var m2 = clientSerializerFactory.GetSerializer <HttpMessageSendChangesRequest>().Deserialize(streamArray); var s2 = await this.WebServerOrchestrator.ApplyThenGetChangesAsync(m2, sessionCache, clientBatchSize, cancellationToken).ConfigureAwait(false); binaryData = clientSerializerFactory.GetSerializer <HttpMessageSendChangesResponse>().Serialize(s2); if (s2.Changes != null && s2.Changes.HasRows) { await this.WebServerOrchestrator.Provider.InterceptAsync(new HttpMessageSendChangesResponseArgs(binaryData)).ConfigureAwait(false); } break; case HttpStep.GetChanges: var m3 = clientSerializerFactory.GetSerializer <HttpMessageGetMoreChangesRequest>().Deserialize(streamArray); var s3 = this.WebServerOrchestrator.GetMoreChanges(m3, sessionCache, cancellationToken); binaryData = clientSerializerFactory.GetSerializer <HttpMessageSendChangesResponse>().Serialize(s3); if (s3.Changes != null && s3.Changes.HasRows) { await this.WebServerOrchestrator.Provider.InterceptAsync(new HttpMessageSendChangesResponseArgs(binaryData)).ConfigureAwait(false); } break; case HttpStep.GetSnapshot: var m4 = clientSerializerFactory.GetSerializer <HttpMessageSendChangesRequest>().Deserialize(streamArray); var s4 = await this.WebServerOrchestrator.GetSnapshotAsync(m4, sessionCache, cancellationToken); binaryData = clientSerializerFactory.GetSerializer <HttpMessageSendChangesResponse>().Serialize(s4); if (s4.Changes != null && s4.Changes.HasRows) { await this.WebServerOrchestrator.Provider.InterceptAsync(new HttpMessageSendChangesResponseArgs(binaryData)).ConfigureAwait(false); } break; } // Save schema to cache with a sliding expiration this.Cache.Set(WebServerProperties.Key, this.WebServerOrchestrator.Schema, this.WebServerProperties.Options.GetServerCacheOptions()); // Save session client to cache with a sliding expiration this.Cache.Set(sessionId, sessionCache, this.WebServerProperties.Options.GetClientCacheOptions()); // Adding the serialization format used and session id httpResponse.Headers.Add("dotmim-sync-session-id", sessionId.ToString()); httpResponse.Headers.Add("dotmim-sync-serialization-format", clientSerializerFactory.Key); // data to send back, as the response byte[] data = this.EnsureCompression(httpRequest, httpResponse, binaryData); await this.GetBody(httpResponse).WriteAsync(data, 0, data.Length).ConfigureAwait(false); } catch (Exception ex) { await WriteExceptionAsync(httpResponse, ex); } finally { //if (httpMessage != null && httpMessage.Step == HttpStep.EndSession) // Cleanup(context.RequestServices.GetService(typeof(IMemoryCache)), syncSessionId); } }
/// <summary> /// Call this method to handle requests on the server, sent by the client /// </summary> public async Task HandleRequestAsync(HttpContext context, Action <RemoteOrchestrator> action, CancellationToken cancellationToken, IProgress <ProgressArgs> progress) { //this.HttpContext = context; var httpRequest = context.Request; var httpResponse = context.Response; var serAndsizeString = string.Empty; var cliConverterKey = string.Empty; // Get the serialization and batch size format if (TryGetHeaderValue(context.Request.Headers, "dotmim-sync-serialization-format", out var vs)) { serAndsizeString = vs.ToLowerInvariant(); } // Get the serialization and batch size format if (TryGetHeaderValue(context.Request.Headers, "dotmim-sync-converter", out var cs)) { cliConverterKey = cs.ToLowerInvariant(); } if (!TryGetHeaderValue(context.Request.Headers, "dotmim-sync-session-id", out var sessionId)) { throw new HttpHeaderMissingExceptiopn("dotmim-sync-session-id"); } if (!TryGetHeaderValue(context.Request.Headers, "dotmim-sync-scope-name", out var scopeName)) { throw new HttpHeaderMissingExceptiopn("dotmim-sync-scope-name"); } if (!TryGetHeaderValue(context.Request.Headers, "dotmim-sync-step", out string iStep)) { throw new HttpHeaderMissingExceptiopn("dotmim-sync-step"); } var step = (HttpStep)Convert.ToInt32(iStep); var readableStream = new MemoryStream(); try { // Copty stream to a readable and seekable stream // HttpRequest.Body is a HttpRequestStream that is readable but can't be Seek await httpRequest.Body.CopyToAsync(readableStream); httpRequest.Body.Close(); httpRequest.Body.Dispose(); // if Hash is present in header, check hash if (TryGetHeaderValue(context.Request.Headers, "dotmim-sync-hash", out string hashStringRequest)) { HashAlgorithm.SHA256.EnsureHash(readableStream, hashStringRequest); } // try get schema from cache if (this.Cache.TryGetValue <SyncSet>(scopeName, out var cachedSchema)) { this.Schema = cachedSchema; } // try get session cache from current sessionId if (!this.Cache.TryGetValue <SessionCache>(sessionId, out var sessionCache)) { sessionCache = new SessionCache(); } // action from user if available action?.Invoke(this); // Get the serializer and batchsize (var clientBatchSize, var clientSerializerFactory) = this.GetClientSerializer(serAndsizeString); // Get converter used by client // Can be null var clientConverter = this.GetClientConverter(cliConverterKey); this.ClientConverter = clientConverter; byte[] binaryData = null; switch (step) { case HttpStep.EnsureScopes: var m1 = await clientSerializerFactory.GetSerializer <HttpMessageEnsureScopesRequest>().DeserializeAsync(readableStream); await this.InterceptAsync(new HttpGettingRequestArgs(context, m1.SyncContext, sessionCache, step), cancellationToken).ConfigureAwait(false); var s1 = await this.EnsureScopesAsync(m1, sessionCache, cancellationToken, progress).ConfigureAwait(false); binaryData = await clientSerializerFactory.GetSerializer <HttpMessageEnsureScopesResponse>().SerializeAsync(s1); break; case HttpStep.EnsureSchema: var m11 = await clientSerializerFactory.GetSerializer <HttpMessageEnsureScopesRequest>().DeserializeAsync(readableStream); await this.InterceptAsync(new HttpGettingRequestArgs(context, m11.SyncContext, sessionCache, step), cancellationToken).ConfigureAwait(false); var s11 = await this.EnsureSchemaAsync(m11, sessionCache, cancellationToken, progress).ConfigureAwait(false); binaryData = await clientSerializerFactory.GetSerializer <HttpMessageEnsureSchemaResponse>().SerializeAsync(s11); break; case HttpStep.SendChanges: var m2 = await clientSerializerFactory.GetSerializer <HttpMessageSendChangesRequest>().DeserializeAsync(readableStream); await this.InterceptAsync(new HttpGettingRequestArgs(context, m2.SyncContext, sessionCache, step), cancellationToken).ConfigureAwait(false); await this.InterceptAsync(new HttpGettingClientChangesArgs(m2, context.Request.Host.Host, sessionCache), cancellationToken).ConfigureAwait(false); var s2 = await this.ApplyThenGetChangesAsync(m2, sessionCache, clientBatchSize, cancellationToken, progress).ConfigureAwait(false); await this.InterceptAsync(new HttpSendingServerChangesArgs(s2, context.Request.Host.Host, sessionCache, false), cancellationToken).ConfigureAwait(false); binaryData = await clientSerializerFactory.GetSerializer <HttpMessageSendChangesResponse>().SerializeAsync(s2); break; case HttpStep.GetChanges: var m3 = await clientSerializerFactory.GetSerializer <HttpMessageSendChangesRequest>().DeserializeAsync(readableStream); await this.InterceptAsync(new HttpGettingRequestArgs(context, m3.SyncContext, sessionCache, step), cancellationToken).ConfigureAwait(false); await this.InterceptAsync(new HttpGettingClientChangesArgs(m3, context.Request.Host.Host, sessionCache), cancellationToken).ConfigureAwait(false); var s3 = await this.GetChangesAsync(m3, sessionCache, clientBatchSize, cancellationToken, progress); await this.InterceptAsync(new HttpSendingServerChangesArgs(s3, context.Request.Host.Host, sessionCache, false), cancellationToken).ConfigureAwait(false); binaryData = await clientSerializerFactory.GetSerializer <HttpMessageSendChangesResponse>().SerializeAsync(s3); break; case HttpStep.GetMoreChanges: var m4 = await clientSerializerFactory.GetSerializer <HttpMessageGetMoreChangesRequest>().DeserializeAsync(readableStream); await this.InterceptAsync(new HttpGettingRequestArgs(context, m4.SyncContext, sessionCache, step), cancellationToken).ConfigureAwait(false); var s4 = await this.GetMoreChangesAsync(m4, sessionCache, cancellationToken, progress); await this.InterceptAsync(new HttpSendingServerChangesArgs(s4, context.Request.Host.Host, sessionCache, false), cancellationToken).ConfigureAwait(false); binaryData = await clientSerializerFactory.GetSerializer <HttpMessageSendChangesResponse>().SerializeAsync(s4); break; case HttpStep.GetSnapshot: var m5 = await clientSerializerFactory.GetSerializer <HttpMessageSendChangesRequest>().DeserializeAsync(readableStream); await this.InterceptAsync(new HttpGettingRequestArgs(context, m5.SyncContext, sessionCache, step), cancellationToken).ConfigureAwait(false); var s5 = await this.GetSnapshotAsync(m5, sessionCache, cancellationToken, progress); await this.InterceptAsync(new HttpSendingServerChangesArgs(s5, context.Request.Host.Host, sessionCache, true), cancellationToken).ConfigureAwait(false); binaryData = await clientSerializerFactory.GetSerializer <HttpMessageSendChangesResponse>().SerializeAsync(s5); break; case HttpStep.GetEstimatedChangesCount: var m6 = await clientSerializerFactory.GetSerializer <HttpMessageSendChangesRequest>().DeserializeAsync(readableStream); await this.InterceptAsync(new HttpGettingRequestArgs(context, m6.SyncContext, sessionCache, step), cancellationToken).ConfigureAwait(false); var s6 = await this.GetEstimatedChangesCountAsync(m6, cancellationToken, progress); await this.InterceptAsync(new HttpSendingServerChangesArgs(s6, context.Request.Host.Host, sessionCache, false), cancellationToken).ConfigureAwait(false); binaryData = await clientSerializerFactory.GetSerializer <HttpMessageSendChangesResponse>().SerializeAsync(s6); break; } // Save schema to cache with a sliding expiration this.Cache.Set(scopeName, this.Schema, this.WebServerOptions.GetServerCacheOptions()); // Save session client to cache with a sliding expiration this.Cache.Set(sessionId, sessionCache, this.WebServerOptions.GetClientCacheOptions()); // Adding the serialization format used and session id httpResponse.Headers.Add("dotmim-sync-session-id", sessionId.ToString()); httpResponse.Headers.Add("dotmim-sync-serialization-format", clientSerializerFactory.Key); // calculate hash var hash = HashAlgorithm.SHA256.Create(binaryData); var hashString = Convert.ToBase64String(hash); // Add hash to header httpResponse.Headers.Add("dotmim-sync-hash", hashString); // data to send back, as the response byte[] data = this.EnsureCompression(httpRequest, httpResponse, binaryData); await this.InterceptAsync(new HttpSendingResponseArgs(context, this.GetContext(), sessionCache, data, step), cancellationToken).ConfigureAwait(false); await httpResponse.Body.WriteAsync(data, 0, data.Length, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { await WebServerManager.WriteExceptionAsync(httpResponse, ex); } finally { readableStream.Flush(); readableStream.Close(); readableStream.Dispose(); } }
/// <summary> /// Get changes from /// </summary> internal async Task <HttpMessageSendChangesResponse> ApplyThenGetChangesAsync(HttpMessageSendChangesRequest httpMessage, SessionCache sessionCache, int clientBatchSize, CancellationToken cancellationToken = default, IProgress <ProgressArgs> progress = null) { // Overriding batch size options value, coming from client // having changes from server in batch size or not is decided by the client. // Basically this options is not used on the server, since it's always overriden by the client this.Options.BatchSize = clientBatchSize; // Get if we need to serialize data or making everything in memory var clientWorkInMemory = clientBatchSize == 0; // Get context from request message var ctx = httpMessage.SyncContext; // Set the context coming from the client this.SetContext(ctx); // Check schema. // If client has stored the schema, the EnsureScope will not be called on server. if (this.Schema == null || !this.Schema.HasTables || !this.Schema.HasColumns) { var serverScopeInfo = await this.EnsureSchemaAsync(cancellationToken, progress).ConfigureAwait(false); this.Schema = serverScopeInfo.Schema; this.Schema.EnsureSchema(); } // ------------------------------------------------------------ // FIRST STEP : receive client changes // ------------------------------------------------------------ // We are receiving changes from client // BatchInfo containing all BatchPartInfo objects // Retrieve batchinfo instance if exists // Get batch info from session cache if exists, otherwise create it if (sessionCache.ClientBatchInfo == null) { sessionCache.ClientBatchInfo = new BatchInfo(clientWorkInMemory, Schema, this.Options.BatchDirectory); } // create the in memory changes set var changesSet = new SyncSet(); foreach (var table in httpMessage.Changes.Tables) { DbSyncAdapter.CreateChangesTable(Schema.Tables[table.TableName, table.SchemaName], changesSet); } changesSet.ImportContainerSet(httpMessage.Changes, false); // If client has made a conversion on each line, apply the reverse side of it if (this.ClientConverter != null && changesSet.HasRows) { AfterDeserializedRows(changesSet, this.ClientConverter); } // add changes to the batch info await sessionCache.ClientBatchInfo.AddChangesAsync(changesSet, httpMessage.BatchIndex, httpMessage.IsLastBatch, this); // Clear the httpMessage set if (!clientWorkInMemory && httpMessage.Changes != null) { httpMessage.Changes.Clear(); } // Until we don't have received all the batches, wait for more if (!httpMessage.IsLastBatch) { return(new HttpMessageSendChangesResponse(httpMessage.SyncContext) { ServerStep = HttpStep.SendChangesInProgress }); } // ------------------------------------------------------------ // SECOND STEP : apply then return server changes // ------------------------------------------------------------ // get changes var(remoteClientTimestamp, serverBatchInfo, _, clientChangesApplied, serverChangesSelected) = await this.ApplyThenGetChangesAsync(httpMessage.Scope, sessionCache.ClientBatchInfo, cancellationToken, progress).ConfigureAwait(false); // Save the server batch info object to cache if not working in memory if (!clientWorkInMemory) { sessionCache.RemoteClientTimestamp = remoteClientTimestamp; sessionCache.ServerBatchInfo = serverBatchInfo; sessionCache.ServerChangesSelected = serverChangesSelected; sessionCache.ClientChangesApplied = clientChangesApplied; } // Get the firt response to send back to client return(await GetChangesResponseAsync(ctx, remoteClientTimestamp, serverBatchInfo, clientChangesApplied, serverChangesSelected, 0)); }
internal async Task <HttpMessageEnsureScopesResponse> EnsureScopesAsync(HttpMessageEnsureScopesRequest httpMessage, SessionCache sessionCache, CancellationToken cancellationToken, IProgress <ProgressArgs> progress = null) { if (httpMessage == null) { throw new ArgumentException("EnsureScopesAsync message could not be null"); } if (this.Setup == null) { throw new ArgumentException("You need to set the tables to sync on server side"); } // Get context from request message var ctx = httpMessage.SyncContext; // Set the context coming from the client this.SetContext(ctx); // Get schema var serverScopeInfo = await this.GetServerScopeAsync(default, default, cancellationToken, progress).ConfigureAwait(false);
/// <summary> /// Get changes from /// </summary> internal async Task <HttpMessageSendChangesResponse> GetSnapshotAsync(HttpMessageSendChangesRequest httpMessage, SessionCache sessionCache, CancellationToken cancellationToken = default, IProgress <ProgressArgs> progress = null) { // TODO : check Snapshot with version and scopename // Check schema. // If client has stored the schema, the EnsureScope will not be called on server. if (this.Schema == null || !this.Schema.HasTables || !this.Schema.HasColumns) { var serverScopeInfo = await this.EnsureSchemaAsync(cancellationToken, progress).ConfigureAwait(false); this.Schema = serverScopeInfo.Schema; this.Schema.EnsureSchema(); } // Get context from request message var ctx = httpMessage.SyncContext; // Set the context coming from the client this.SetContext(ctx); // get changes var snap = await this.GetSnapshotAsync(this.Schema, cancellationToken, progress).ConfigureAwait(false); // if no snapshot, return empty response if (snap.ServerBatchInfo == null) { var changesResponse = new HttpMessageSendChangesResponse(ctx); changesResponse.ServerStep = HttpStep.GetSnapshot; changesResponse.BatchIndex = 0; changesResponse.IsLastBatch = true; changesResponse.RemoteClientTimestamp = 0; changesResponse.Changes = null; return(changesResponse); } sessionCache.RemoteClientTimestamp = snap.RemoteClientTimestamp; sessionCache.ServerBatchInfo = snap.ServerBatchInfo; // Get the firt response to send back to client return(await GetChangesResponseAsync(ctx, snap.RemoteClientTimestamp, snap.ServerBatchInfo, null, null, 0)); }