Skip to content

Commit a257cd5

Browse files
committed
Core - Add OwinResourceHandler and OwinSchemeHandlerFactory
Very basic implementation ported from https://github.com/amaitland/CefSharp.Owin - Currently no multi part post support - No support for cancellation token
1 parent 6fa25ab commit a257cd5

File tree

3 files changed

+256
-2
lines changed

3 files changed

+256
-2
lines changed

CefSharp/CefSharp.csproj

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<?xml version="1.0" encoding="utf-8"?>
1+
<?xml version="1.0" encoding="utf-8"?>
22
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
33
<Import Project="..\packages\GitLink.3.1.0\build\GitLink.props" Condition="Exists('..\packages\GitLink.3.1.0\build\GitLink.props')" />
44
<Import Project="..\packages\Microsoft.Net.Compilers.2.4.0\build\Microsoft.Net.Compilers.props" Condition="Exists('..\packages\Microsoft.Net.Compilers.2.4.0\build\Microsoft.Net.Compilers.props')" />
@@ -171,6 +171,8 @@
171171
<Compile Include="RenderProcess\V8Exception.cs" />
172172
<Compile Include="RequestContextExtensions.cs" />
173173
<Compile Include="ResponseFilter\StreamResponseFilter.cs" />
174+
<Compile Include="SchemeHandler\OwinResourceHandler.cs" />
175+
<Compile Include="SchemeHandler\OwinSchemeHandlerFactory.cs" />
174176
<Compile Include="Structs\AudioParamaters.cs" />
175177
<Compile Include="Structs\TouchEvent.cs" />
176178
<Compile Include="CefLibraryHandle.cs" />
@@ -353,4 +355,4 @@
353355
<Target Name="AfterBuild">
354356
</Target>
355357
-->
356-
</Project>
358+
</Project>
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
// Copyright © 2021 The CefSharp Authors. All rights reserved.
2+
//
3+
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
4+
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Collections.Specialized;
8+
using System.IO;
9+
using System.Linq;
10+
using System.Net;
11+
using System.Threading.Tasks;
12+
13+
namespace CefSharp.SchemeHandler
14+
{
15+
//Shorthand for Owin pipeline func
16+
using AppFunc = Func<IDictionary<string, object>, Task>;
17+
18+
/// <summary>
19+
/// <see cref="ResourceHandler"/> implementation that uses an OWIN capable host of fulfilling requests.
20+
/// Can be used with NancyFx or AspNet Core
21+
/// </summary>
22+
/// TODO:
23+
/// - Multipart post data
24+
/// - Cancellation Token
25+
public class OwinResourceHandler : ResourceHandler
26+
{
27+
private static readonly Dictionary<int, string> StatusCodeToStatusTextMapping = new Dictionary<int, string>
28+
{
29+
{200, "OK"},
30+
{301, "Moved Permanently"},
31+
{304, "Not Modified"},
32+
{404, "Not Found"}
33+
};
34+
35+
private readonly AppFunc appFunc;
36+
37+
public OwinResourceHandler(AppFunc appFunc)
38+
{
39+
this.appFunc = appFunc;
40+
}
41+
42+
/// <summary>
43+
/// Read the request, then process it through the OWEN pipeline
44+
/// then populate the response properties.
45+
/// </summary>
46+
/// <param name="request">request</param>
47+
/// <param name="callback">callback</param>
48+
/// <returns>always returns true as we'll handle all requests this handler is registered for.</returns>
49+
public override CefReturnValue ProcessRequestAsync(IRequest request, ICallback callback)
50+
{
51+
// PART 1 - Read the request - here we read the request and create a dictionary
52+
// that follows the OWEN standard
53+
54+
var responseStream = new MemoryStream();
55+
var requestBody = Stream.Null;
56+
57+
if (request.Method == "POST")
58+
{
59+
using (var postData = request.PostData)
60+
{
61+
if (postData != null)
62+
{
63+
var postDataElements = postData.Elements;
64+
65+
66+
var firstPostDataElement = postDataElements.First();
67+
68+
var bytes = firstPostDataElement.Bytes;
69+
70+
requestBody = new MemoryStream(bytes, 0, bytes.Length);
71+
72+
//TODO: Investigate how to process multi part POST data
73+
//var charSet = request.GetCharSet();
74+
//foreach (var element in elements)
75+
//{
76+
// if (element.Type == PostDataElementType.Bytes)
77+
// {
78+
// var body = element.GetBody(charSet);
79+
// }
80+
//}
81+
}
82+
}
83+
}
84+
85+
//var cancellationTokenSource = new CancellationTokenSource();
86+
//var cancellationToken = cancellationTokenSource.Token;
87+
var uri = new Uri(request.Url);
88+
var requestHeaders = ToDictionary(request.Headers);
89+
//Add Host header as per http://owin.org/html/owin.html#5-2-hostname
90+
requestHeaders.Add("Host", new[] { uri.Host + (uri.Port > 0 ? (":" + uri.Port) : "") });
91+
92+
//http://owin.org/html/owin.html#3-2-environment
93+
//The Environment dictionary stores information about the request,
94+
//the response, and any relevant server state.
95+
//The server is responsible for providing body streams and header collections for both the request and response in the initial call.
96+
//The application then populates the appropriate fields with response data, writes the response body, and returns when done.
97+
//Keys MUST be compared using StringComparer.Ordinal.
98+
var owinEnvironment = new Dictionary<string, object>(StringComparer.Ordinal)
99+
{
100+
//Request http://owin.org/html/owin.html#3-2-1-request-data
101+
{"owin.RequestBody", requestBody},
102+
{"owin.RequestHeaders", requestHeaders},
103+
{"owin.RequestMethod", request.Method},
104+
{"owin.RequestPath", uri.AbsolutePath},
105+
{"owin.RequestPathBase", "/"},
106+
{"owin.RequestProtocol", "HTTP/1.1"},
107+
{"owin.RequestQueryString", uri.Query},
108+
{"owin.RequestScheme", uri.Scheme},
109+
//Response http://owin.org/html/owin.html#3-2-2-response-data
110+
{"owin.ResponseHeaders", new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase)},
111+
{"owin.ResponseBody", responseStream},
112+
//Other Data
113+
{"owin.Version", "1.0.0"},
114+
//{"owin.CallCancelled", cancellationToken}
115+
};
116+
117+
//PART 2 - Spawn a new task to execute the OWIN pipeline
118+
//We execute this in an async fashion and return true so other processing
119+
//can occur
120+
Task.Run(async () =>
121+
{
122+
//Call into the OWEN pipeline
123+
try
124+
{
125+
await appFunc(owinEnvironment);
126+
127+
//Response has been populated - reset the position to 0 so it can be read
128+
responseStream.Position = 0;
129+
130+
int statusCode;
131+
132+
if (owinEnvironment.ContainsKey("owin.ResponseStatusCode"))
133+
{
134+
statusCode = Convert.ToInt32(owinEnvironment["owin.ResponseStatusCode"]);
135+
//TODO: Improve status code mapping - see if CEF has a helper function that can be exposed
136+
//StatusText = StatusCodeToStatusTextMapping[response.StatusCode];
137+
}
138+
else
139+
{
140+
statusCode = (int)HttpStatusCode.OK;
141+
//StatusText = "OK";
142+
}
143+
144+
//Grab a reference to the ResponseHeaders
145+
var responseHeaders = (Dictionary<string, string[]>)owinEnvironment["owin.ResponseHeaders"];
146+
147+
//Populate the response properties
148+
Stream = responseStream;
149+
ResponseLength = responseStream.Length;
150+
StatusCode = statusCode;
151+
152+
if(responseHeaders.ContainsKey("Content-Type"))
153+
{
154+
var contentType = responseHeaders["Content-Type"].First();
155+
MimeType = contentType.Split(';').First();
156+
}
157+
else
158+
{
159+
MimeType = DefaultMimeType;
160+
}
161+
162+
//Add the response headers from OWIN to the Headers NameValueCollection
163+
foreach (var responseHeader in responseHeaders)
164+
{
165+
//It's possible for headers to have multiple values
166+
foreach (var val in responseHeader.Value)
167+
{
168+
Headers.Add(responseHeader.Key, val);
169+
}
170+
}
171+
}
172+
catch(Exception ex)
173+
{
174+
int statusCode = (int)HttpStatusCode.InternalServerError;
175+
176+
var responseData = GetByteArray("Error: " + ex.ToString(), System.Text.Encoding.UTF8, true);
177+
178+
//Populate the response properties
179+
Stream = new MemoryStream(responseData);
180+
ResponseLength = responseData.Length;
181+
StatusCode = statusCode;
182+
MimeType = "text/html";
183+
}
184+
185+
//Once we've finished populating the properties we execute the callback
186+
//Callback wraps an unmanaged resource, so let's explicitly Dispose when we're done
187+
using (callback)
188+
{
189+
callback.Continue();
190+
}
191+
});
192+
193+
return CefReturnValue.ContinueAsync;
194+
}
195+
196+
private static IDictionary<string, string[]> ToDictionary(NameValueCollection nameValueCollection)
197+
{
198+
var dict = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);
199+
foreach (var key in nameValueCollection.AllKeys)
200+
{
201+
if (!dict.ContainsKey(key))
202+
{
203+
dict.Add(key, new string[0]);
204+
}
205+
var strings = nameValueCollection.GetValues(key);
206+
if (strings == null)
207+
{
208+
continue;
209+
}
210+
foreach (string value in strings)
211+
{
212+
var values = dict[key].ToList();
213+
values.Add(value);
214+
dict[key] = values.ToArray();
215+
}
216+
}
217+
return dict;
218+
}
219+
}
220+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright © 2021 The CefSharp Authors. All rights reserved.
2+
//
3+
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
4+
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Threading.Tasks;
8+
9+
namespace CefSharp.SchemeHandler
10+
{
11+
//Shorthand for Owin pipeline func
12+
using AppFunc = Func<IDictionary<string, object>, Task>;
13+
14+
/// <summary>
15+
/// <see cref="ISchemeHandlerFactory"/> implementation that takes an OWIN AppFunc
16+
/// and uses an <see cref="OwinResourceHandler"/> to fulfill each requests.
17+
/// </summary>
18+
public class OwinSchemeHandlerFactory : ISchemeHandlerFactory
19+
{
20+
private readonly AppFunc appFunc;
21+
22+
public OwinSchemeHandlerFactory(AppFunc appFunc)
23+
{
24+
this.appFunc = appFunc;
25+
}
26+
27+
public IResourceHandler Create(IBrowser browser, IFrame frame, string schemeName, IRequest request)
28+
{
29+
return new OwinResourceHandler(appFunc);
30+
}
31+
}
32+
}

0 commit comments

Comments
 (0)