ChromiumDotNet / Chromium.AspNetCore.Bridge

Chromium.AspNetCore.Bridge
Apache License 2.0
25 stars 9 forks source link

Add example compatible with sessions #8

Open suehshtri opened 1 year ago

suehshtri commented 1 year ago

Consider adding an example that is compatible with server code that uses a session and session cookie.

marman-hp commented 11 months ago

Hello, First I want to thank @amaitland for this library. This is very useful.

Well I think is not about the compatible session example, but the session is always null, because OnStarting in SessionMiddleware never triggered. the OnStarting is responsible for sending session to cookie.

My Startup.cs ---ConfigureServices -- > services.AddDistributedMemoryCache(); services.AddSession(opt => { opt.Cookie.HttpOnly = true; opt.Cookie.IsEssential = true; opt.IdleTimeout = TimeSpan.FromHours(1); });

---Configure -- > app.UseSession();

image

HttpContext.Session.SetString("hi", "Hello world"); --> doesn't work , because "Session" never sent to cookie by SessionMiddleware.

So I find out. SessionMiddleware uses OnStarting to write session cookies, but it's never triggered by the owin server I made a simple test middleware using OnStarting and the result OnStarting never triggered ,

I think All middleware that implements OnStarting/OnComplete is never triggered by owin server. (my oppinion about the flow, OnStarting/OnComplete Triggerd by StartAsync, StartAsync Triggered by RequestDelegate, since it use Owin, StartAsync must be called manually on the server, if I'am not wrong).

so I checked implementation of IHttpResponseFeature, I found the IHttpResponseFeature.OnStarting is empty, it should be implemented. I made small change in OwinFeatureImpl.cs and OwinServer.cs.

OwinFeatureImpl.cs ` public class OwinFeatureImpl : IHttpRequestFeature, IHttpResponseFeature, IHttpResponseBodyFeature, IHttpRequestIdentifierFeature, IOwinEnvironmentFeature { /// /// Gets or sets OWIN environment values. /// public IDictionary Environment { get; set; } private PipeWriter _responseBodyWrapper; private IHeaderDictionary _requestHeaders; private IHeaderDictionary _responseHeaders; private Func _responseStartingAsync = () => Task.CompletedTask; private Func _responseCompletedAsync = () => Task.CompletedTask; private bool _started; /// /// Initializes a new instance of . /// /// The environment values. public OwinFeatureImpl(IDictionary environment) { Environment = environment; } private T GetEnvironmentPropertyOrDefault(string key) { object value; if (Environment.TryGetValue(key, out value) && value is T) { return (T)value; } return default(T); } private void SetEnvironmentProperty(string key, object value) { Environment[key] = value; } string IHttpRequestFeature.Protocol { get { return GetEnvironmentPropertyOrDefault(OwinConstants.RequestProtocol); } set { SetEnvironmentProperty(OwinConstants.RequestProtocol, value); } } string IHttpRequestFeature.Scheme { get { return GetEnvironmentPropertyOrDefault(OwinConstants.RequestScheme); } set { SetEnvironmentProperty(OwinConstants.RequestScheme, value); } } string IHttpRequestFeature.Method { get { return GetEnvironmentPropertyOrDefault(OwinConstants.RequestMethod); } set { SetEnvironmentProperty(OwinConstants.RequestMethod, value); } } string IHttpRequestFeature.PathBase { get { return GetEnvironmentPropertyOrDefault(OwinConstants.RequestPathBase); } set { SetEnvironmentProperty(OwinConstants.RequestPathBase, value); } } string IHttpRequestFeature.Path { get { return GetEnvironmentPropertyOrDefault(OwinConstants.RequestPath); } set { SetEnvironmentProperty(OwinConstants.RequestPath, value); } } string IHttpRequestFeature.QueryString { get { return AddQuestionMark(GetEnvironmentPropertyOrDefault(OwinConstants.RequestQueryString)); } set { SetEnvironmentProperty(OwinConstants.RequestQueryString, RemoveQuestionMark(value)); } } string IHttpRequestFeature.RawTarget { get { return string.Empty; } set { throw new NotSupportedException(); } } IHeaderDictionary IHttpRequestFeature.Headers { get { if(_requestHeaders == null) { var dict = GetEnvironmentPropertyOrDefault>(OwinConstants.RequestHeaders); _requestHeaders = dict is IHeaderDictionary ? (IHeaderDictionary)dict : new DictionaryStringValuesWrapper(dict); } return _requestHeaders; } set { SetEnvironmentProperty(OwinConstants.RequestHeaders, MakeDictionaryStringArray(value)); } } string IHttpRequestIdentifierFeature.TraceIdentifier { get { return GetEnvironmentPropertyOrDefault(OwinConstants.RequestId); } set { SetEnvironmentProperty(OwinConstants.RequestId, value); } } Stream IHttpRequestFeature.Body { get { return GetEnvironmentPropertyOrDefault(OwinConstants.RequestBody); } set { SetEnvironmentProperty(OwinConstants.RequestBody, value); } } int IHttpResponseFeature.StatusCode { get { return GetEnvironmentPropertyOrDefault(OwinConstants.ResponseStatusCode); } set { SetEnvironmentProperty(OwinConstants.ResponseStatusCode, value); } } string IHttpResponseFeature.ReasonPhrase { get { return GetEnvironmentPropertyOrDefault(OwinConstants.ResponseReasonPhrase); } set { SetEnvironmentProperty(OwinConstants.ResponseReasonPhrase, value); } } IHeaderDictionary IHttpResponseFeature.Headers { get { if (_responseHeaders == null) { var dict = GetEnvironmentPropertyOrDefault>(OwinConstants.ResponseHeaders); _responseHeaders = dict is IHeaderDictionary ? (IHeaderDictionary)dict : new DictionaryStringValuesWrapper(dict); } return _responseHeaders; } set { SetEnvironmentProperty(OwinConstants.ResponseHeaders, MakeDictionaryStringArray(value)); } } Stream IHttpResponseFeature.Body { get { return GetEnvironmentPropertyOrDefault(OwinConstants.ResponseBody); } set { SetEnvironmentProperty(OwinConstants.ResponseBody, value); } } Stream IHttpResponseBodyFeature.Stream { get { return GetEnvironmentPropertyOrDefault(OwinConstants.ResponseBody); } } PipeWriter IHttpResponseBodyFeature.Writer { get { if (_responseBodyWrapper == null) { _responseBodyWrapper = PipeWriter.Create(GetEnvironmentPropertyOrDefault(OwinConstants.ResponseBody), new StreamPipeWriterOptions(leaveOpen: true)); } return _responseBodyWrapper; } } bool IHttpResponseFeature.HasStarted => false; void IHttpResponseFeature.OnStarting(Func callback, object state) { //not protecting with HasStarted because is always false var prior = _responseStartingAsync; _responseStartingAsync = async () => { await callback(state); await prior(); }; } void IHttpResponseFeature.OnCompleted(Func callback, object state) { //not protecting with HasStarted because is always false var prior = _responseCompletedAsync; _responseCompletedAsync = async () => { try { await callback(state); } finally { await prior(); } }; } Task IHttpResponseBodyFeature.SendFileAsync(string path, long offset, long? length, CancellationToken cancellation) { var writer = ((IHttpResponseBodyFeature)this).Writer; return SendFileFallback.SendFileAsync(writer.AsStream(), path, offset, length, cancellation); } void IHttpResponseBodyFeature.DisableBuffering() { } async Task FireOnSendingHeadersAsync() { try { await _responseStartingAsync(); } finally { //HasStarted = true; } } Task FireOnResponseCompletedAsync() { return _responseCompletedAsync(); } async Task IHttpResponseBodyFeature.StartAsync(CancellationToken cancellationToken) { try { _started = true; await FireOnSendingHeadersAsync(); } catch (Exception) { throw; } if (_responseBodyWrapper != null) { await _responseBodyWrapper.FlushAsync(cancellationToken); } //// The pipe may or may not have flushed the stream. Make sure the stream gets flushed to trigger response start. await GetEnvironmentPropertyOrDefault(OwinConstants.ResponseBody).FlushAsync(cancellationToken); } Task IHttpResponseBodyFeature.CompleteAsync() { if (!_started) { ((IHttpResponseBodyFeature)this).StartAsync().Wait(); } FireOnResponseCompletedAsync(); if (_responseBodyWrapper != null) { _responseBodyWrapper.FlushAsync().ConfigureAwait(false); } return ((IHttpResponseBodyFeature)this).Writer.CompleteAsync().AsTask(); } /// public void Dispose() { } private static string RemoveQuestionMark(string queryString) { if (!string.IsNullOrEmpty(queryString)) { if (queryString[0] == '?') { return queryString.Substring(1); } } return queryString; } private static string AddQuestionMark(string queryString) { if (!string.IsNullOrEmpty(queryString)) { return '?' + queryString; } return queryString; } private static IDictionary MakeDictionaryStringArray(IHeaderDictionary dictionary) { var wrapper = dictionary as DictionaryStringValuesWrapper; if (wrapper != null) { return wrapper.Inner; } return new DictionaryStringArrayWrapper(dictionary); } } `
OwinServer.cs ` public class OwinServer : IServer { private IFeatureCollection _features = new FeatureCollection(); private Action useOwin; IFeatureCollection IServer.Features { get { return _features; } } void IDisposable.Dispose() { } /// /// The will be called when the OWIN /// is ready for use. /// /// called when OWIN is ready for use. public void UseOwin(Action action) { useOwin = action; } Task IServer.StartAsync(IHttpApplication application, CancellationToken cancellationToken) { AppFunc appFunc = async env => { var features = new OwinFeatureCollection(env); var context = application.CreateContext(features); try { await application.ProcessRequestAsync(context); //Normaly startasync triggered by RequestDelegate by invoking HttpContext //when default httpcontext create by owinserver, startasync has never been triggered , //so I need to triggerd manualy await features.Get().StartAsync(); } catch (Exception ex) { application.DisposeContext(context, ex); throw; } application.DisposeContext(context, null); }; useOwin?.Invoke(appFunc); return Task.CompletedTask; } Task IServer.StopAsync(CancellationToken cancellationToken) { return Task.CompletedTask; } } `

and now my session are working, tested with chromly and webview2 sample

image

I hope this help if someone have the same issue with "onstarting middleware" especialy SessionMiddleware(UseSession).

Thanks.

amaitland commented 11 months ago

@marman-hp Thanks for posting your findings 👍

Can you please submit your changes as a PR so they can be included in the library itself. Thanks.

marman-hp commented 11 months ago

I have create fork from your repository , and make minor changes, added missing ability with IHttpResponseFeature, so that anyone can test or investigate the code.