/// <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); }
/// <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); }
/// <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; }