-
-
Save t0yv0/1055424 to your computer and use it in GitHub Desktop.
(* | |
// # FSCGI - F# Common Gateway Interface | |
// | |
// This gist is a response to OWIN <http://owin.org/>. | |
// This gist is public domain. | |
// | |
// See also the discussion stating OWIN rationale and how it is better than FSCGI: | |
// http://groups.google.com/group/net-http-abstractions/browse_thread/thread/ac3d7c1e3d43c1d4 | |
// | |
// ## Problem Summary | |
// | |
// The goal of OWIN is to provide a low-level .NET standard for web apps to | |
// communicate with their environments, filling the similar role as Java | |
// Servlet Specification, FastCGI, SCGI (Python) and the like. | |
// | |
// OWIN as of Mar 13, 2011 is problematic in the following respects: | |
// | |
// * poor use of static typing, for example uses Dictionary<String,Object> | |
// * gratuitous complexity of the definition (nested Func<_> callbacks) | |
// * reliance on prose to communicate invariants that can be type-enforced | |
// | |
// ## Solution Summary | |
// | |
// * use more explicit typing | |
// * use an iteratee-based representation for the IO process | |
// * simplify exception handling by the rule: application code MUST NOT throw | |
// any exceptions; doing so is indicating a programming error and will be | |
// treated in a host-dependent way. | |
// | |
*) | |
/// Defines the common gateway interface protocol for F#. | |
namespace FSCGI | |
type Data = System.ArraySegment<byte> | |
type Headers = Map<string,string> | |
type StatusCode = int | |
type Status = string | |
type Request = | |
{ | |
Headers : Headers | |
Method : string | |
Path : string | |
PathBase : string | |
QueryString : string | |
Scheme : string | |
} | |
type State = | |
| Closed | |
| Open | |
type Writer = | |
| Done | |
| Write of (State -> Data * Writer) | |
type Response = | |
| Read of (option<Data> -> Response) | |
| Respond of StatusCode * Status * Headers * Writer | |
type Application = | |
Request -> Response |
/// Converts structural encodings to proper FSCGI.* types. | |
module FSCGI.Structural.Converter | |
type private Encodings<'R,'W> = | |
('W -> Writer<'W>) * | |
('R -> Response<'R,'W>) | |
let private ConvertRequest (r : FSCGI.Request) : Request = | |
( | |
r.Headers, | |
r.Method, | |
r.Path, | |
r.PathBase, | |
r.QueryString, | |
r.Scheme | |
) | |
let rec private ConvertWriter ((eW, _) as enc : Encodings<'R,'W>) | |
(w: Writer<'W>) : FSCGI.Writer = | |
match w with | |
| Choice1Of2 () -> | |
FSCGI.Done | |
| Choice2Of2 f -> | |
FSCGI.Write (fun state -> | |
let isOpen = | |
match state with | |
| FSCGI.Closed -> false | |
| FSCGI.Open -> true | |
let (data, writer) = f isOpen | |
(data, ConvertWriter enc (eW writer))) | |
let rec private ConvertResponse ((eW, eR) as enc : Encodings<'R,'W>) | |
(r: Response<'R,'W>) : FSCGI.Response = | |
match r with | |
| Choice1Of2 f -> | |
FSCGI.Read (fun d -> ConvertResponse enc (eR (f d))) | |
| Choice2Of2 (a, b, c, d) -> | |
FSCGI.Respond (a, b, c, ConvertWriter enc (eW d)) | |
let Convert ((eW, eR, run): Application<'R,'W>) : FSCGI.Application = | |
ConvertResponse (eW, eR) << run << ConvertRequest |
/// Provides structural encodings for the FSCGI.* types. | |
namespace FSCGI.Structural | |
type Data = System.ArraySegment<byte> | |
type HeaderName = string | |
type HeaderValue = string | |
type Headers = Map<HeaderName,HeaderValue> | |
type Method = string | |
type Path = string | |
type PathBase = string | |
type QueryString = string | |
type Scheme = string | |
type StatusCode = int | |
type Status = string | |
type Request = | |
Headers * Method * Path * PathBase * QueryString * Scheme | |
type Writer<'W> = | |
Choice< | |
unit, | |
bool -> Data * 'W | |
> | |
type Response<'R,'W> = | |
Choice< | |
option<Data> -> 'R, | |
StatusCode * Status * Headers * 'W | |
> | |
type Application<'R,'W> = | |
('W -> Writer<'W>) * | |
('R -> Response<'R,'W>) * | |
(Request -> Response<'R,'W>) |
Hmm... I suppose the application could use Async.RunWithContinuations
and supply the Response
write mechanism as a callback. That should still facilitate what you've described above. The application would then control any internal asynchronicity.
Spot on, the "check back in a bit" scenario is prohibited. The application will be stuck to blocking (doing Async.RunSynchronously) if it wants to talk to the network or the database.
If we allow this "check back in a bit", I think the interface quickly becomes isomorphic to OWIN..
What I am struggling to grasp right now - is are these "check back in a bit" scenarios really safe? Or, how do we write apps that are safe?
It is just so easy to trip. Consider Petricek's AsyncSeq: readInBlocks
is broken because it does not close the file descriptor. If it were to close it, when would it? And how can the reader signal lack of interest in the rest of the sequence?
Lots to think about. With Fracture, we have the pipeline model which uses agents to progress things along. We could always leverage that within Frank to allow for delayed or long-running work to not block the current stuff. Of course, if we have a number of agents running, they are not preventing the primary server from blocking anyway, so we're probably safe. Another aspect we are planning is to have load management be able to spin up new agents as necessary, so again, blocking within a given application shouldn't affect the overall system.
The reason I added
AsyncBody
versions of theBody
types in the new Frank signatures was to provide applications the ability to say, "I need to do some look-ups on my own; check back with me in a bit." Your examples would be useful in the case that an application is doing some immediate results, but I don't think it addresses the common need to call out to a database or web service from the server. Or am I missing that aspect?