I'm running an OWIN self-hosted application that hosts a REST API and also allows websocket connectivity for real-time data. I'm using WebAPI to handle the routing and mapping of routes to controllers.
When I use Web API to handle the websocket routes the socket is closed as soon as the controller returns. However, if I create my own middleware the socket does not close.
I'd prefer to use Web API for all of my routes. But more importantly I want to understand what's going on. I don't like my production code to work without understanding why it's working.
Here is the relevant Web API code snippet:
public class WebServer : IDisposable
{
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
config.Routes.MapHttpRoute(
name: "Websocket",
routeTemplate: "ws/all",
defaults: new { controller = "MyWebSocket", action = "Get" });
app.UseWebApi(config);
}
}
public class MyWebSocketController : System.Web.Http.ApiController
{
public IHttpActionResult Get()
{
var owinContext = Request.GetOwinContext();
var accept = owinContext.Get<Action<IDictionary<string, object>, Func<IDictionary<string, object>, Task>>>("websocket.Accept");
accept(null, RunWebSocket);
return Ok();
}
private async Task RunWebSocket(IDictionary<string, object> websocketContext)
{
WebSocket socket;
if (websocketContext.TryGetValue(typeof(System.Net.WebSockets.WebSocketContext).FullName, out value))
{
socket = ((System.Net.WebSockets.WebSocketContext)value).WebSocket;
}
ArraySegment<Byte> buffer = new ArraySegment<byte>(new Byte[128]);
WebSocketReceiveResult result = null;
using (var ms = new MemoryStream())
{
while (socket.State == WebSocketState.Open)
{
ms.SetLength(0);
do
{
result = await socket.ReceiveAsync(buffer, CancellationToken.None);
ms.Write(buffer.Array, buffer.Offset, result.Count);
}
while (!result.EndOfMessage);
if (result.MessageType == WebSocketMessageType.Close)
{
// Close socket
}
ms.Seek(0, SeekOrigin.Begin);
if (result.MessageType == WebSocketMessageType.Text)
{
// Handle message
}
}
}
}
}
The call to ReceiveAsync schedules a continuation. The Get method returns back to the ApiController which closes the connection, which also closes the websocket.
Here is the relevant code for the OWIN middleware.
public class WebServer : IDisposable
{
public void Configuration(IAppBuilder app)
{
app.Use<WebSocketMiddleware>();
}
}
public class WebSocketMiddleware : OwinMiddleware
{
public override Task Invoke(IOwinContext context)
{
var accept = context.Get<Action<IDictionary<string, object>, Func<IDictionary<string, object>, Task>>>("websocket.Accept");
accept(null, RunWebSocket);
return;
}
private async Task RunWebSocket(IDictionary<string, object> websocketContext)
{
// Same as Web API implementation
}
}
Again the continuation is scheduled during the call to ReceiveAsync and the Invoke method returns. However, the connection remains open and I'm able to send and receive data through the websocket.
So, I have a solution, but I'd really love to understand what's going on. Any references would be greatly appreciated.
Edit: Actually the socket is closed in both cases. The web API version sends a RST from the server, as if the connection was abruptly closed, while the OWIN version experiences a normal FIN ACK. However the web API doesn't allow any further communication over the websocket, while the OWIN version does. So I'm not really sure how this is supposed to work.
Related
I'm attempting to convert an old WCF service to an ASP.NET Core Web API, making use of the CoreWCF package. A key feature of this existing service is that it's being self hosted by an other application and is able to gracefully start & stop, without creating memory leaks.
I have been able to figure out how to start and stop a prototype service. However, after performing some stress testing, it does seem like I've left a memory leak somewhere and I'm sadly out of ideas or available documentation at this point. I'm also considering that an ASP.NET Core Web API just isn't supposed to be used like this and I misunderstood this, if so, be sure to let me know. Also my apologies for the truckload of code, but I'm not sure what's relevant or not to the question.
The code for my prototype service looks like this:
Configuring the webhost:
private void CreateWebHostBuilder(){
host = WebHost.CreateDefaultBuilder()
.UseKestrel(options =>
{
options.AllowSynchronousIO = true;
options.ListenLocalhost(Startup.PORT_NR);
options.ConfigureHttpsDefaults(
options => options.ClientCertificateMode = ClientCertificateMode.RequireCertificate
);
})
.ConfigureLogging(logging => { logging.SetMinimumLevel(LogLevel.Warning); })
.UseSetting(WebHostDefaults.DetailedErrorsKey, "true")
.UseShutdownTimeout(TimeSpan.FromSeconds(1))
.UseStartup<Startup>()
.Build();
}
Inside the Startup class:
Configuring the IApplicationBuilder:
public void Configure(IApplicationBuilder app){
app.UseServiceModel(builder =>
{
// Add the Echo Service
builder.AddService<EchoService>()
// Add service web endpoint
.AddServiceWebEndpoint<EchoService, IEchoService>(
WEB_API_PATH,behavior => { behavior.HelpEnabled = true;}
);
});
app.UseMiddleware<SwaggerMiddleware>();
app.UseSwaggerUI();
app.UseAuthentication();
}
Configuring the services:
public void ConfigureServices(IServiceCollection services){
services.AddServiceModelWebServices()
.AddHostedService<EchoService>()
.AddSingleton(new SwaggerOptions())
.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate();
}
The service interface:
[ServiceContract]
[OpenApiBasePath($"/{Startup.WEB_API_PATH}")]
public interface IEchoService : IHostedService {
[OperationContract]
[WebGet(UriTemplate = "/hello")]
[OpenApiOperation(Description = "Method used to receive a friendly \"Hello world\"",
Summary = "Hello world")]
[OpenApiResponse(Description = "OK Response", StatusCode = HttpStatusCode.OK)]
string HelloWorld();
}
The implemented service:
public class EchoService : IEchoService {
public EchoService() { }
public string HelloWorld() {
return "Hello world!";
}
public Task StartAsync(CancellationToken cancellationToken) {
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
Creating and starting the host + services:
public void StartWebService(object obj){
CreateWebHostBuilder();
host.StartAsync();
}
Stopping and disposing the services and host:
public void StopWebService(object obj) {
host.StopAsync().Wait();
host.Dispose();
}
So if anyone has any suggestions or tutorial reference, be sure to let me know, any help is welcome.
I have front-end(script) and back-end(Spring-Boot) code.
In backend code:
#GetMapping("/calldata")
public Response call() {
...//Imagine this operations take more than 5 minutes.
}
In font-end code:
I just call this backend api and open socket and wait until data is ready in loading state.
Is there a way to say from backend to frontend; "Don't wait to me. I will notify to you when I am ready. And I will serve my data."?
You want you request to be handled asynchronously. You can use websockets which keeps a single persistent connection open between server and client.
https://www.baeldung.com/spring-websockets-sendtouser
I had the same problem and my solution include a combination of WebSocket and Async programming. The good thing about this approach is, you can still call your REST endpoint normally. I am using SpringBoot and Angular 9. Here is what I did:
Create an async service on BE
Create WebSocket on BE
Create WebSocket on FE
Create a common topic and let FB and BE listen to it, where BE will push the response and FE and read from it.
Create a void controller method and call the async service's method
a. By doing this, your FE will not wait for the server response and your async service can continue to process the request.
Once your service is done processing, push the response to a websocket topic
Listen to the topic on your FE, and once BE pushes the response you'll be able to handle it on FE.
Here is the sample code:
Index.html:
<script>
var global = global || window;
var Buffer = Buffer || [];
var process = process || {
env: { DEBUG: undefined },
version: []
};
</script>
FE WebSocket congif file:
import * as Stomp from 'stompjs';
import * as SockJS from 'sockjs-client';
export class WebSocketAPI {
// localWebSocketEndpoint = 'http://localhost:8080/ws';
webSocketEndpoint = '/ws';
topic = '/topic/greetings'; // this is the topic which will be used to exchagne data
stompClient: any;
constructor() { }
connect() {
let ws = new SockJS(this.webSocketEndpoint);
this.stompClient = Stomp.over(ws);
const that = this;
that.stompClient.connect({}, function (frame) {
that.stompClient.subscribe(that.topic, function (sdkEvent) {
that.onMessageReceived(sdkEvent);
})
})
}
disconnect() {
if (this.stompClient !== null) {
this.stompClient.disconnect();
}
}
// you don't need this
send(name) {
this.stompClient.send('/app/hello', {}, JSON.stringify({name: name}));
}
// this is where you will receive your data once Server is done process
onMessageReceived(message) {
console.log('received: ', message);
// this.app.handleMessage(message.body);
}
}
BE Controller method:
#GetMapping("/calldata")
#ResponseStatus(value = HttpStatus.OK)
#LogExecutionTime
public void call() {
asyncService.processAsync();
}
AsyncService:
#Service
public class AsyncService {
#Autowired
private SimpMessagingTemplate simpMessagingTemplate;
#LogExecutionTime
#Async("asyncExecutor")
public void processAsync() {
// do your processing and push the response to the topic
simpMessagingTemplate.convertAndSend("/topic/greetings", response);
}
}
WebSocketConfig:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic");
registry.setApplicationDestinationPrefixes("/app");
}
}
And finally AsyncConfig:
#Configuration
#EnableAsync
public class AsyncConfiguration {
#Bean(name = "asyncExecutor")
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(3);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("AsynchThread-");
executor.initialize();
return executor;
}
}
Hope this will help you as well.
Is there a way to capture the response from requests served by OAuth? Specifically, I need to log the request and response from OAuthAuthorizationServerProvider.GrantResourceOwnerCredentials().
I've tried extending OwinMiddleware and overriding Invoke as shown in this post, but I'm unable to read the response body. I'd like to use a message handler as this post demonstrates, but I don't have UseHttpMessageHandler on my AppBuilder object.
Any help would be greatly appreciated.
Thank you!
Update
Modifying the example provided in Brock's excellent video, here's what I need to do:
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.Use(typeof(MW1));
app.Map("/api", fooApp => {
fooApp.Use<MW2>();
});
}
}
public class MW1 {
Func<IDictionary<string, object>, Task> next;
public MW1(Func<IDictionary<string, object>, Task> next) {
this.next = next;
}
public async Task Invoke(IDictionary<string, object> env) {
var ctx = new OwinContext(env);
await next(env);
// I need to be able to read: <h1>MW2 called</h1> written by MW2
var body = ctx.Response.Body;
// body.CanRead = False
}
}
public class MW2 {
Func<IDictionary<string, object>, Task> next;
public MW2(Func<IDictionary<string, object>, Task> next) {
this.next = next;
}
public async Task Invoke(IDictionary<string, object> env) {
var ctx = new OwinContext(env);
await ctx.Response.WriteAsync("<h1>MW2 called</h1>");
await next(env);
}
}
I actually need to read the response sent from the OAuth provider, but I assume it would be the same process.
Why not implement an OWIN middleware component that sits in front of the OAuth AS middleware?
In a console application, I would like to use a service that would normally need the current http context to be passed to its constructor. I am using Ninject, and I think I can simply fake an http context and define the proper binding, but I have been struggling with this for a few hours without success.
The details:
The service is actually a mailing service that comes from an ASP.Net MVC project. I am also using Ninject for IoC. The mail service needs the current http context to be passed to its constructor. I do the binding as follows:
kernel.Bind<IMyEmailService>().To<MyEmailService>()
.WithConstructorArgument("httpContext", ninjectContext => new HttpContextWrapper(HttpContext.Current));
However, I would like now to use this mailing service in a console application that will be used to run automated tasks at night. In order to do this, I think I can simply fake an http context, but I have been struggling for a few hours with this.
All the mailing service needs from the context are these two properties:
httpContext.Request.UserHostAddress
httpContext.Request.RawUrl
I thought I could do something like this, but:
Define my own fake request class:
public class AutomatedTaskHttpRequest : SimpleWorkerRequest
{
public string UserHostAddress;
public string RawUrl;
public AutomatedTaskHttpRequest(string appVirtualDir, string appPhysicalDir, string page, string query, TextWriter output)
: base(appVirtualDir, appPhysicalDir, page, query, output)
{
this.UserHostAddress = "127.0.0.1";
this.RawUrl = null;
}
}
Define my own context class:
public class AutomatedTasksHttpContext
{
public AutomatedTaskHttpRequest Request;
public AutomatedTasksHttpContext()
{
this.Request = new AutomatedTaskHttpRequest("", "", "", null, new StringWriter());
}
}
and bind it as follows in my console application:
kernel.Bind<IUpDirEmailService>().To<UpDirEmailService>()
.WithConstructorArgument("httpContext", ninjectContext => new AutomatedTasksHttpContext());
Unfortunately, this is not working out. I tried various variants, but none was working. Please bear with me. All that IoC stuff is quite new to me.
I'd answered recently about using a HttpContextFactory for testing, which takes a different approach equally to a console application.
public static class HttpContextFactory
{
[ThreadStatic]
private static HttpContextBase _serviceHttpContext;
public static void SetHttpContext(HttpContextBase httpContextBase)
{
_serviceHttpContext = httpContextBase;
}
public static HttpContextBase GetHttpContext()
{
if (_serviceHttpContext!= null)
{
return _serviceHttpContext;
}
if (HttpContext.Current != null)
{
return new HttpContextWrapper(HttpContext.Current);
}
return null;
}
}
then in your code to this:
var rawUrl = HttpContextFactory.GetHttpContext().Request.RawUrl;
then in your tests use the property as a seam
HttpContextFactory.SetHttpContext(HttpMocks.HttpContext());
where HttpMocks has the following and would be adjusted for your tests:
public static HttpContextBase HttpContext()
{
var context = MockRepository.GenerateMock<HttpContextBase>();
context.Stub(r => r.Request).Return(HttpRequest());
// and stub out whatever else you need to, like session etc
return context;
}
public static HttpRequestBase HttpRequest()
{
var httpRequest = MockRepository.GenerateMock<HttpRequestBase>();
httpRequest.Stub(r => r.UserHostAddress).Return("127.0.0.1");
httpRequest.Stub(r => r.RawUrl).Return(null);
return httpRequest;
}
My project has a need for realtime user interaction and I think SignalR will solve my need. I'm technically on a SharePoint 2007 project, although I'm exclusively in application pages and thus barely use SharePoint at all. Regardless, I'm stuck in a 2.0 framework app pool in IIS.
My first approach was to try to create a 4.0 application as a sub-site. Unfortunately, that failed miserably. That approach works in a non-SharePoint world, but it appears that SharePoint has hijacked too much of the request pipeline for this approach to work for me.
So now I'm going down the path of creating a separate IIS Site that's 4.0 and using IIS rewrite rules to fake my app into thinking a particular subdirectory (/realtime/) is local and not a separate site so that I don't have to deal with cross domain request issues. The problem is I can't get IIS rewrite rules to rewrite to another http host (e.g. http://www.mySharepoint.com/_layouts/MySite/realtime/Hello.aspx to http://realtime.mySharePoint.com/Hello.aspx).
Any help with approach #1 or approach #2 or any alternative ideas would be greatly appreciated.
Here is what I did... Web App with signalR .net4.0, then your SharePoint Web App .net 2.
Add this to the global.asax in your Signalr project
RouteTable.Routes.MapHttpHandlerRoute("spproxy","spproxy/{*operation}", new SharePointRProxyHandler());
If you want to raise an event from SharePoint you can do a http POST to this new route URL for example
http://localhost:38262/spproxy
It will pass any posted data onto the httphandler below, that will then broadcast it to your clients.
Here is the code for MapHttpHandlerRoute
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace System.Web.Routing
{
public class HttpHandlerRoute : IRouteHandler
{
private String _virtualPath = null;
private IHttpHandler _handler = null;
public HttpHandlerRoute(String virtualPath)
{
_virtualPath = virtualPath;
}
public HttpHandlerRoute(IHttpHandler handler)
{
_handler = handler;
}
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
IHttpHandler result;
if (_handler == null)
{
result = (IHttpHandler)System.Web.Compilation.BuildManager.CreateInstanceFromVirtualPath(_virtualPath, typeof(IHttpHandler));
}
else
{
result = _handler;
}
return result;
}
}
public static class RoutingExtensions
{
public static void MapHttpHandlerRoute(this RouteCollection routes, string routeName, string routeUrl, string physicalFile, RouteValueDictionary defaults = null, RouteValueDictionary constraints = null)
{
var route = new Route(routeUrl, defaults, constraints, new HttpHandlerRoute(physicalFile));
RouteTable.Routes.Add(routeName, route);
}
public static void MapHttpHandlerRoute(this RouteCollection routes, string routeName, string routeUrl, IHttpHandler handler, RouteValueDictionary defaults = null, RouteValueDictionary constraints = null)
{
var route = new Route(routeUrl, defaults, constraints, new HttpHandlerRoute(handler));
RouteTable.Routes.Add(routeName, route);
}
}
}
Or you could just post directly to a httphandler and get the handler to do a connection.Broadcast
namespace SharePointRProxy
{
/// <summary>
/// Summary description for SharePointRProxyHandler
/// </summary>
public class SharePointRProxyHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "text/plain";
IConnectionManager connectonManager = AspNetHost.DependencyResolver.Resolve<IConnectionManager>();
IConnection connection = connectonManager.GetConnection<MyConnection>();
object payload = null; //Add payload here 'context.Request.Params["data"] ?'
JavaScriptSerializer jss = new JavaScriptSerializer();
var payloadJSON = jss.Serialize(payload);
connection.Broadcast(payloadJSON);
}
public bool IsReusable
{
get
{
return false;
}
}
}
}
You could also use either an event handler calling a .net 4.0 web service or an http handler to grab requests from SharePoint and pass them over to a .net 4.0 application running your signalr code.
You can see an example of using an http handler here: http://spmatt.wordpress.com/2012/04/12/harnessing-signalr-in-sharepoint/