/// <summary>Process an HTTP GET in FHIR R4</summary>
        /// <param name="context"> [in,out] The context.</param>
        /// <param name="response">[in,out] The response.</param>
        internal static void ProcessGet(ref HttpContext context, ref HttpResponseMessage response)
        {
            // check for an ID
            string requestUrl = context.Request.Path;

            if (requestUrl.EndsWith('/'))
            {
                requestUrl = requestUrl.Substring(0, requestUrl.Length - 1);
            }

            string id = requestUrl.Substring(requestUrl.LastIndexOf('/') + 1);

            if (id.ToLowerInvariant() == "subscription")
            {
                ProcessorUtils.SerializeR4(ref response, SubscriptionManagerR4.GetSubscriptionsBundle());
            }
            else if (SubscriptionManagerR4.TryGetSubscription(id, out Subscription foundSub))
            {
                ProcessorUtils.SerializeR4(ref response, foundSub);
            }
            else
            {
                response.StatusCode = HttpStatusCode.NotFound;
            }
        }
        /// <summary>Adds a subscription to client to 'clientGuid'.</summary>
        /// <param name="subscriptionId">Identifier for the subscription.</param>
        /// <param name="clientGuid">    Unique identifier for the client.</param>
        /// <returns>True if it succeeds, false if it fails.</returns>
        public static bool AddSubscriptionToClient(string subscriptionId, Guid clientGuid)
        {
            if (string.IsNullOrEmpty(subscriptionId))
            {
                return(false);
            }

            if ((!SubscriptionManagerR4.Exists(subscriptionId)) &&
                (!SubscriptionManagerR5.Exists(subscriptionId)))
            {
                return(false);
            }

            if (!_instance._guidInfoDict.ContainsKey(clientGuid))
            {
                return(false);
            }

            _instance._guidInfoDict[clientGuid].SubscriptionIdSet.Add(subscriptionId);

            if (!_instance._subscriptionInfosDict.ContainsKey(subscriptionId))
            {
                _instance._subscriptionInfosDict.Add(subscriptionId, new List <WebsocketClientInformation>());
            }

            _instance._subscriptionInfosDict[subscriptionId].Add(_instance._guidInfoDict[clientGuid]);
            return(true);
        }
        /// <summary>Process the operation get ws binding token described by response.</summary>
        /// <param name="context">          [in,out] The context.</param>
        /// <param name="response">         [in,out] The response.</param>
        /// <param name="previousComponent">The previous component.</param>
        internal static void ProcessOperationGetWsBindingToken(
            ref HttpContext context,
            ref HttpResponseMessage response,
            string previousComponent)
        {
            List <string> ids = new List <string>();

            if (previousComponent != "Subscription")
            {
                ids.Add(previousComponent);
            }
            else
            {
                try
                {
                    using (TextReader reader = new StreamReader(context.Request.Body))
                    {
                        string requestContent = reader.ReadToEndAsync().Result;

                        Parameters opParams = _fhirParser.Parse <Parameters>(requestContent);

                        foreach (Parameters.ParameterComponent param in opParams.Parameter)
                        {
                            if (param.Name == "ids")
                            {
                                ids.Add((param.Value as Id).ToString());
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    response.StatusCode = HttpStatusCode.BadRequest;
                    response.Content    = new StringContent("Caught exception: " + ex.Message);
                    return;
                }
            }

            foreach (string id in ids)
            {
                if (!SubscriptionManagerR4.Exists(id))
                {
                    response.StatusCode = HttpStatusCode.BadRequest;
                    response.Content    = new StringContent($"Invalid subscription id: {id}");
                    return;
                }
            }

            SubscriptionWsBindingToken token = SubscriptionWsBindingToken.GetTokenR4(ids);

            WebsocketManager.RegisterToken(token);

            Parameters parameters = new Parameters();

            parameters.Add("token", new FhirString(token.Token.ToString()));
            parameters.Add("expiration", new FhirDateTime(new DateTimeOffset(token.ExpiresAt)));
            parameters.Add("subscriptions", new FhirString(string.Join(',', ids)));

            ProcessorUtils.SerializeR4(ref response, parameters);
        }
        /// <summary>Main entry-point for this application.</summary>
        /// <param name="args">An array of command-line argument strings.</param>
        public static void Main(string[] args)
        {
            // setup our configuration (command line > environment > appsettings.json)
            Configuration = new ConfigurationBuilder()
                            .AddJsonFile("appsettings.json", optional: true)
                            .AddEnvironmentVariables()
                            .Build()
            ;

            // update configuration to make sure listen url is properly formatted
            Regex regex = new Regex(_regexBaseUrlMatch);
            Match match = regex.Match(Configuration["Server_Public_Url"]);

            Configuration["Server_Public_Url"] = match.ToString();
            PublicUrl = match.ToString();

            match = regex.Match(Configuration["Server_Internal_Url"]);
            Configuration["Server_Internal_Url"] = match.ToString();

            // update external urls to make sure the DO have trailing slashes
            if (!Configuration["Server_FHIR_Url_R4"].EndsWith('/'))
            {
                Configuration["Server_FHIR_Url_R4"] = Configuration["Server_FHIR_Url_R4"] + '/';
            }

            FhirServerUrlR4 = Configuration["Server_FHIR_Url_R4"];

            // update external urls to make sure the DO have trailing slashes
            if (!Configuration["Server_FHIR_Url_R5"].EndsWith('/'))
            {
                Configuration["Server_FHIR_Url_R5"] = Configuration["Server_FHIR_Url_R5"] + '/';
            }

            FhirServerUrlR5 = Configuration["Server_FHIR_Url_R5"];

            // create our REST client
            RestClient = new HttpClient();

            // initialize managers
            SubscriptionTopicManager.Init();
            SubscriptionManagerR4.Init();
            SubscriptionManagerR5.Init();
            WebsocketManager.Init();

            // create our service host
            CreateHostBuilder(args).Build().StartAsync();

            // create our web host
            CreateWebHostBuilder(args).Build().Run();
        }
        /// <summary>Process the operation status R4.</summary>
        /// <param name="context">          [in,out] The context.</param>
        /// <param name="response">         [in,out] The response.</param>
        /// <param name="previousComponent">The previous component.</param>
        internal static void ProcessOperationStatus(
            ref HttpContext context,
            ref HttpResponseMessage response,
            string previousComponent)
        {
            List <string> ids = new List <string>();

            if (previousComponent != "Subscription")
            {
                ids.Add(previousComponent);
            }
            else
            {
                foreach (KeyValuePair <string, StringValues> query in context.Request.Query)
                {
                    if (query.Key == "ids")
                    {
                        ids.AddRange(query.Value);
                    }
                }
            }

            // create a bundle for this message message
            Bundle bundle = new Bundle()
            {
                Type      = Bundle.BundleType.Searchset,
                Timestamp = new DateTimeOffset(DateTime.Now),
                Meta      = new Meta(),
                Entry     = new List <Bundle.EntryComponent>(),
            };

            foreach (string id in ids)
            {
                if (SubscriptionManagerR4.TryGetSubscriptionStatus(id, out Parameters status, 0, true))
                {
                    bundle.Entry.Add(new Bundle.EntryComponent()
                    {
                        FullUrl  = Program.UrlForR5ResourceId(status.TypeName, status.Id),
                        Resource = status,
                        Search   = new Bundle.SearchComponent()
                        {
                            Mode = Bundle.SearchEntryMode.Match,
                        },
                    });
                }
            }

            ProcessorUtils.SerializeR4(ref response, bundle);
        }
Пример #6
0
        /// <summary>Process the request.</summary>
        /// <param name="context">The context.</param>
        /// <returns>An asynchronous result that yields a HttpResponseMessage.</returns>
        internal static async Task <HttpResponseMessage> Process(HttpContext context)
        {
            string fhirServerUrl = ProcessorUtils.GetFhirServerUrlR4(context.Request);

            if (context.Request.Path.Value.Length > 4)
            {
                context.Request.Path = new PathString(context.Request.Path.Value.Substring(3));
            }

            // context.Request.Headers["Accept-Encoding"] = "";
            // proxy this call
            ForwardContext proxiedContext = context.ForwardTo(fhirServerUrl);

            // send to server and await response
            HttpResponseMessage response = await proxiedContext.Send().ConfigureAwait(false);

            // get copies of data when we care
            switch (context.Request.Method.ToUpperInvariant())
            {
            case "PUT":
            case "POST":

                // grab the message body to look at
                string responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

                if (response.IsSuccessStatusCode)
                {
                    // run this Encounter through our Subscription Manager
                    SubscriptionManagerR4.ProcessEncounter(
                        responseContent,
                        response.Headers.Location);
                }

                break;

            default:

                // ignore
                break;
            }

            // return the results of the proxied call
            return(response);
        }
Пример #7
0
        /// <summary>Reads client messages.</summary>
        /// <param name="webSocket">  The web socket.</param>
        /// <param name="clientGuid"> Unique identifier for the client.</param>
        /// <param name="cancelToken">A token that allows processing to be cancelled.</param>
        /// <returns>An asynchronous result.</returns>
        private async Task ReadClientMessages(
            WebSocket webSocket,
            Guid clientGuid,
            CancellationToken cancelToken)
        {
            // get the client object
            if (!WebsocketManager.TryGetClient(clientGuid, out WebsocketClientInformation client))
            {
                // nothing to do here (will cancel on exit)
                return;
            }

            // create our receive buffer
            byte[] buffer = new byte[_messageBufferSize];
            int    count;
            int    messageLength = 0;

            WebSocketReceiveResult result;

            // loop until cancelled
            while (!cancelToken.IsCancellationRequested)
            {
                // reset buffer offset
                messageLength = 0;

                // do not bubble errors here
                try
                {
                    // read a message
                    do
                    {
                        count  = _messageBufferSize - messageLength;
                        result = await webSocket.ReceiveAsync(
                            new ArraySegment <byte>(
                                buffer,
                                messageLength,
                                count),
                            cancelToken).ConfigureAwait(false);

                        messageLength += result.Count;
                    }while (!result.EndOfMessage);

                    // process this message
                    if (result.MessageType == WebSocketMessageType.Close)
                    {
                        break;
                    }

                    // check for a bind request
                    string message = Encoding.UTF8.GetString(buffer).Substring(0, messageLength);

                    if (message.StartsWith("bind ", StringComparison.Ordinal))
                    {
                        // grab the rest of the content
                        message = message.Replace("bind ", string.Empty, StringComparison.Ordinal);

                        string[] ids = message.Split(',');

                        // traverse the requested ids
                        foreach (string id in ids)
                        {
                            string subscriptionId = id.StartsWith("Subscription/", StringComparison.Ordinal)
                                ? id.Replace("Subscription/", string.Empty, StringComparison.Ordinal)
                                : id;

                            // make sure this subscription exists
                            if ((!SubscriptionManagerR4.Exists(subscriptionId)) &&
                                (!SubscriptionManagerR5.Exists(subscriptionId)))
                            {
                                continue;
                            }

                            // register this subscription to this client
                            WebsocketManager.AddSubscriptionToClient(subscriptionId, clientGuid);
                        }
                    }

                    if (message.StartsWith("bind-with-token ", StringComparison.Ordinal))
                    {
                        // grab the rest of the content
                        message = message.Replace("bind-with-token ", string.Empty, StringComparison.Ordinal);

                        if (!Guid.TryParse(message, out Guid tokenGuid))
                        {
                            continue;
                        }

                        // register this token to this client
                        WebsocketManager.BindClientWithToken(tokenGuid, clientGuid);
                    }

                    if (message.StartsWith("unbind ", StringComparison.Ordinal))
                    {
                        // grab the rest of the content
                        message = message.Replace("unbind ", string.Empty, StringComparison.Ordinal);

                        string[] ids = message.Split(',');

                        // traverse the requested ids
                        foreach (string id in ids)
                        {
                            string subscriptionId = id.StartsWith("Subscription/", StringComparison.Ordinal)
                                ? id.Replace("Subscription/", string.Empty, StringComparison.Ordinal)
                                : id;

                            // remove this subscription from this client
                            WebsocketManager.RemoveSubscriptionFromClient(subscriptionId, clientGuid);
                        }
                    }
                }

                // keep looping
                catch (Exception ex)
                {
                    Console.WriteLine($"SubscriptionWebsocketHandler.ReadClientMessages" +
                                      $" <<< client: {clientGuid} caught exception: {ex.Message}");

                    // this socket is borked, exit
                    break;
                }
            }
        }
        /// <summary>Process an HTTP POST for FHIR R4</summary>
        /// <param name="context"> [in,out] The context.</param>
        /// <param name="response">[in,out] The response.</param>
        internal static void ProcessPost(ref HttpContext context, ref HttpResponseMessage response)
        {
            StringContent localResponse;

            // default to returning the representation if not specified
            string preferredResponse = "return=representation";

            // check for headers we are interested in
            foreach (KeyValuePair <string, StringValues> kvp in context.Request.Headers)
            {
                if (kvp.Key.ToLowerInvariant() == "prefer")
                {
                    preferredResponse = kvp.Value;
                }
            }

            string requestContent;

            // grab the message body to look at
            using (System.IO.StreamReader requestReader = new System.IO.StreamReader(context.Request.Body))
            {
                requestContent = requestReader.ReadToEndAsync().Result;
            }

            // check to see if the manager does anything with this text
            SubscriptionManagerR4.HandlePost(
                requestContent,
                out Subscription subscription,
                out HttpStatusCode statusCode,
                out string failureContent);

            // check for errors
            if (statusCode != HttpStatusCode.Created)
            {
                switch (preferredResponse)
                {
                case "return=minimal":
                    localResponse = new StringContent(string.Empty, Encoding.UTF8, "text/plain");
                    break;

                case "return=OperationOutcome":
                    OperationOutcome outcome = new OperationOutcome()
                    {
                        Id    = Guid.NewGuid().ToString(),
                        Issue = new List <OperationOutcome.IssueComponent>()
                        {
                            new OperationOutcome.IssueComponent()
                            {
                                Severity    = OperationOutcome.IssueSeverity.Error,
                                Code        = OperationOutcome.IssueType.Unknown,
                                Diagnostics = failureContent,
                            },
                        },
                    };
                    localResponse = new StringContent(
                        _fhirSerializer.SerializeToString(outcome),
                        Encoding.UTF8,
                        "application/fhir+json");

                    break;

                default:
                    localResponse = new StringContent(failureContent, Encoding.UTF8, "text/plain");
                    break;
                }

                response.Content    = localResponse;
                response.StatusCode = statusCode;

                return;
            }

            // figure out our link to this resource
            string url = Program.UrlForR4ResourceId("Subscription", subscription.Id);

            switch (preferredResponse)
            {
            case "return=minimal":
                localResponse = new StringContent(string.Empty, Encoding.UTF8, "text/plain");
                break;

            case "return=OperationOutcome":
                OperationOutcome outcome = new OperationOutcome()
                {
                    Id    = Guid.NewGuid().ToString(),
                    Issue = new List <OperationOutcome.IssueComponent>()
                    {
                        new OperationOutcome.IssueComponent()
                        {
                            Severity = OperationOutcome.IssueSeverity.Information,
                            Code     = OperationOutcome.IssueType.Value,
                        },
                    },
                };
                localResponse = new StringContent(
                    _fhirSerializer.SerializeToString(outcome),
                    Encoding.UTF8,
                    "application/fhir+json");

                break;

            default:
                localResponse = new StringContent(
                    _fhirSerializer.SerializeToString(subscription),
                    Encoding.UTF8,
                    "application/fhir+json");
                break;
            }

            response.Headers.Add("Location", url);
            response.Headers.Add("Access-Control-Expose-Headers", "Location,ETag");
            response.Content    = localResponse;
            response.StatusCode = HttpStatusCode.Created;
        }