/// <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);
            }
        }
Exemple #2
0
        /// <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();
            }
        }
Exemple #3
0
        /// <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));
        }
Exemple #4
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);
Exemple #5
0
        /// <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));
        }