Open foretspaisibles opened 9 years ago
Thanks for the suggestions. I have a few resources I use internally related to serving static files as well as directories. I've been meaning to add them here but since they depend on a filesystem interface may need special treatment for both async and lwt.
I wanted to try crunch
available with opam, that prepares a OCaml module containing a file-system hierarchy. For things like CSS, JS-Frameworks and the like, it might be appropriated.
I worked on this and built up a working example, serving pages built with bootstrap.
There is a point that still needs to be clarified, since I am not sure what is the best way to set the content-type header on the response.
My content_types_provided
looked like this:
method content_types_provided rd =
self#log_headers rd;
Wm.continue [
("*/*" , self#to_data);
] rd
and the #to_data
method ended with
Wm.continue (`String data) (self#set_mime_type rd)
where #set_mime_type
uses Rd.with_resp_headers
to set the "content-type" value according to the path being retrieved (trust the path suffix). This does not work as expected, examining the issue with curl shows that the header is set two times:
< HTTP/1.1 200 OK
< content-length: 23409
< content-type: text/css
< content-type: */*
I therefore assume that the consumer of the result of content_types_provided
method sets the content-type header itself.
What would be the best way to serve static resources without building a-priori knowledge of the type of static resources being served in the class? My goal would be to write a generic class parametrised by an association list mapping paths to content which could be used to serve static content. A possibility would be to generate the answer of the #content_types_provided
method using that association list and a dictionary mapping globbing patterns to mime-types, but there might be abetter way.
BTW are you interested in seeing such a class becoming a part of the webmachine (e.g. as lib/webmachine_Static.ml
) or would you prefer seeing this in a separate project?
Here's the resource implementation I use for serving a single static file. It necessarily relies on core and async since it does filesystem I/O. It also uses the magic-mime library to determine content types based on file extensions. Ideally, an implementation for lwt and async would exist in corresponding subpackages, which would also export the applied Webmachine functor for each of the libraries.
open Core.Std
open Async.Std
let serve_file filename rd =
Reader.open_file filename
>>| Reader.pipe
>>= fun pipe ->
continue (`Pipe pipe) rd
class file filename = object(self)
inherit [Body.t] resource
val content_type = Magic_mime.lookup filename
val stat_struct = stat_file filename
val methods = [`GET; `HEAD; `OPTIONS]
method private to_content rd =
serve_file filename rd
method resource_exists rd =
match stat_struct with
| `Stat _ -> continue true rd
| _ -> continue false rd
method forbidden rd =
match stat_struct with
| `Forbidden -> continue true rd
| _ -> continue false rd
method options rd =
continue ["content-type", content_type] rd
method allowed_methods rd =
continue methods rd
method content_types_accepted rd =
continue [] rd
method content_types_provided rd =
continue [content_type, self#to_content] rd
end
@seliopou offtopic and so opinion based, but why do you use classes? I've been writing some OCaml using async
, lwt
and cohttp
libraries, but classes and objects are still so foreign to me. I always considered them as abandoned features of OCaml, and they seem to be uncommon now.
A good rule of thumb to decide wether to use functions or objects to solve a problem is that functions shine when there is a few types and a lot of treatments while objects are used at their best when there are few treatments and a lot of different types or objects on which to use these treatments.
Here we are in the case where we have few treatments and a lot of possible types or objects, so, at this level of the discussion, it is sound to use objects for this. Besides this, I understand this library is a port of an Erlang project, using classes could have made the port easier. @seliopou Am I right?
There are two uses of the object system that should be distinguished. The first is its use in the implementation of the decision diagram. This use is not directly exposed to the user, and was indeed a matter of expediency while porting. Specifically, open recursion allowed me to write the node handlers in the logic code in whichever order came to mind. This use can be eliminated by topologically sorting the handlers according to their call graph and moving all the state within the logic class to a separate data type. This use may have some performance impact, but shouldn't affect the user much at all.
The second is the use of a virtual class for defining resources. This was an intentional choice. Using a virtual class neatly accomplishes the following goals:
To elaborate on the fourth point, using classes allows one to reuse code via inheritance. Now I'm no OOP zealot, but this method of code reuse is a great fit when dealing with what are essentially handlers attached to hierarchical paths. Usually the entities down the hierarchy utilize parts of the handler logic without modification, while sometimes parts of the handler logic are augmented with additional checks or processing. I leverage this all over the place internally, to great effect.
Now in theory, you could do this with modules, signatures, and functors. The cost you pay for that is a minor proliferation of signatures and functors in order to handle default-only and complete modules.
And to comment briefly on the third point, one way that this port of webmachine differs from the Erlang implementation is that only request/response information is threaded through the decision diagram in a purely functional way. Any application state that may exist has to be managed within the resource class. This was also an intentional decision in order to minimize the number of type variables that users had to deal with—'body Rd.t
would become ('a, 'body) Rd.t
and that would then cascade into practically every type.
In terms of any perception of classes or objects being abandoned features, my understanding of feature evolution in OCaml is that once it's in the language, it's in the language. Whenever I've heard of people working on new features and tooling for the language, they are always sure to address objects. Perhaps begrudgingly, but they still do it. So while their use may not be widespread, they're definitely not going to be removed from the language any time soon.
Hope this helps. Maybe I should put this in a wiki.
It would be interesting to show how to prepare a simple class serving static content. I will take a look at this.