Skip to content

Commit

Permalink
Merge pull request #10 from avsm/master
Browse files Browse the repository at this point in the history
0.9.14
  • Loading branch information
avsm committed Dec 15, 2013
2 parents 7e128fc + 8400e38 commit 8b6ab28
Show file tree
Hide file tree
Showing 38 changed files with 6,041 additions and 985 deletions.
3 changes: 2 additions & 1 deletion .travis-ci.sh
Expand Up @@ -20,7 +20,8 @@ echo OPAM versions
opam --version
opam --git-version

opam init git://github.com/ocaml/opam-repository >/dev/null 2>&1
# opam init git://github.com/ocaml/opam-repository >/dev/null 2>&1
opam init
opam install ${OPAM_DEPENDS}

eval `opam config env`
Expand Down
10 changes: 10 additions & 0 deletions CHANGES
@@ -1,3 +1,13 @@
0.9.14 (2013-12-15):
* Install a `cohttp-server` binary that serves local directory contents via a web server (#54).
* Add a `flush` function to the `IO` module type and implement in Lwt/Async/Mirage.
* Add option `flush` support in the Async and Lwt responders (#52).
* Autogenerate HTTP codes from @citricsquid's JSON representation of the HTTP RFCs.
* Always set `TCP_NODELAY` for Lwt/Unix server sockets for low-latency responses (#58).
* Added a Server-Side Events test-case from the HTML5 Doctor. See `lib_test/README.md`.
* Async.Server response now takes an optional `body` rather than a mandatory `body option` (#62).
* Regenerate build system using OASIS 0.4.0.

0.9.13 (2013-12-10):
* The `cohttp.lwt-core` is now installed as an OS-independent Lwt library.
* Add support for Mirage 1.0, via `cohttp.mirage-unix` and `cohttp.mirage-xen`.
Expand Down
19 changes: 13 additions & 6 deletions README.md
Expand Up @@ -4,16 +4,23 @@ libraries:

* `Cohttp_lwt_unix` uses the [Lwt](http://ocsigen.org/lwt) library, and
specifically the UNIX bindings.
* `Cohttp_async` uses the [Async](https://bitbucket.org/yminsky/ocaml-core/wiki/DummiesGuideToAsync)
library from Jane Street.
* `Cohttp_lwt_mirage` uses the [Mirage](http://www.openmirage.org) interface
* `Cohttp_async` uses the [Async](https://realworldocaml.org/v1/en/html/concurrent-programming-with-async.html)
library.
* `Cohttp_mirage` uses the [Mirage](http://www.openmirage.org) interface
to generate standalone microkernels.

You can implement other targets using parser very easily. Look at the
`lib/IO.ml` signature, and implement that in the desired backend.
You can implement other targets using the parser very easily. Look at the
`lib/IO.mli` signature and implement that in the desired backend.

You can activate some runtime debugging by setting `COHTTP_DEBUG` to any
value, and all requests and responses will be written to stderr.

For build requirements, please see the `_oasis` file, or use OPAM to install
it from http://github.com/OCamlPro/opam
it from <http://github.com/ocaml/opam>.

## Simple HTTP server

If you install the Async dependency, then a `cohttp-server` binary will also be
built and installed that acts in a similar fashion to the Python
`SimpleHTTPServer`. Just run `cohttp-server` in a directory and it will
open up a local port and serve the files over HTTP.
20 changes: 10 additions & 10 deletions _oasis
@@ -1,9 +1,9 @@
OASISFormat: 0.3
Name: cohttp
Version: 0.9.13
Version: 0.9.14
Synopsis: HTTP library for Lwt, Async and Mirage
Authors: Anil Madhavapeddy, Stefano Zacchiroli, David Sheets, Thomas Gazagnaire, David Scott
License: LGPL-2.0 with OCaml linking exception
License: ISC
Plugins: META (0.3)
BuildTools: ocamlbuild

Expand Down Expand Up @@ -241,14 +241,14 @@ Executable test_net_async_server
Install: false
BuildDepends: cohttp, cohttp.async, oUnit (>= 1.0.2)

#Executable "cohttp-async-server"
# Path: bin
# MainIs: cohttp_server_async.ml
# Build$: flag(tests) && flag(async)
# Custom: true
# CompiledObject: best
# Install: true
# BuildDepends: cohttp, cohttp.async
Executable "cohttp-server"
Path: bin
MainIs: cohttp_server_async.ml
Build$: flag(tests) && flag(async)
Custom: true
CompiledObject: best
Install: true
BuildDepends: cohttp, cohttp.async

Test test_accept
Run$: flag(tests)
Expand Down
34 changes: 27 additions & 7 deletions _tags
@@ -1,7 +1,7 @@
# OASIS_START
# DO NOT EDIT (digest: 191919a1b412de614ac25c71f18ab0da)
# Ignore VCS directories, you can use the same kind of rule outside
# OASIS_START/STOP if you want to exclude directories that contains
# DO NOT EDIT (digest: 34cfc630efe139056434de798589c0f0)
# Ignore VCS directories, you can use the same kind of rule outside
# OASIS_START/STOP if you want to exclude directories that contains
# useless stuff for the build process
<**/.svn>: -traverse
<**/.svn>: not_hygienic
Expand Down Expand Up @@ -282,9 +282,29 @@
<lib_test/*.ml{,i}>: pkg_fieldslib
<lib_test/*.ml{,i}>: pkg_fieldslib.syntax
<lib_test/test_net_async_server.{native,byte}>: custom
# Executable cohttp-server
<bin/cohttp_server_async.{native,byte}>: use_cohttp_async
<bin/cohttp_server_async.{native,byte}>: use_cohttp
<bin/cohttp_server_async.{native,byte}>: pkg_uri
<bin/cohttp_server_async.{native,byte}>: pkg_async_core
<bin/cohttp_server_async.{native,byte}>: pkg_async_unix
<bin/cohttp_server_async.{native,byte}>: pkg_threads
<bin/cohttp_server_async.{native,byte}>: pkg_async
<bin/cohttp_server_async.{native,byte}>: pkg_re
<bin/cohttp_server_async.{native,byte}>: pkg_uri.services
<bin/cohttp_server_async.{native,byte}>: pkg_fieldslib
<bin/cohttp_server_async.{native,byte}>: pkg_fieldslib.syntax
<bin/*.ml{,i}>: use_cohttp_async
<bin/*.ml{,i}>: use_cohttp
<bin/*.ml{,i}>: pkg_uri
<bin/*.ml{,i}>: pkg_async_core
<bin/*.ml{,i}>: pkg_async_unix
<bin/*.ml{,i}>: pkg_threads
<bin/*.ml{,i}>: pkg_async
<bin/*.ml{,i}>: pkg_re
<bin/*.ml{,i}>: pkg_uri.services
<bin/*.ml{,i}>: pkg_fieldslib
<bin/*.ml{,i}>: pkg_fieldslib.syntax
<bin/cohttp_server_async.{native,byte}>: custom
# OASIS_STOP
<lib_test/{test_parser,test_net_lwt,test_net_lwt_server,test_net_mirage_server,test_net_lwt_client_and_server}.ml>: syntax_camlp4o, pkg_lwt.syntax
<cohttp/request.*> or <cohttp/response.*> or <cohttp/cookie.*>: syntax_camlp4o, pkg_fieldslib.syntax
<lwt/*>: syntax_camlp4o, pkg_lwt.syntax
<mirage/*>: syntax_camlp4o, pkg_lwt.syntax
true: annot, bin_annot, debug, strict_sequence, principal
33 changes: 23 additions & 10 deletions async/cohttp_async.ml
Expand Up @@ -89,6 +89,9 @@ module IO = struct
Writer.write oc "\r\n";
return ()
)

let flush oc =
Writer.flushed oc
end

module Net = struct
Expand Down Expand Up @@ -238,33 +241,43 @@ module Server = struct
>>= fun response ->
response wr

let respond ?headers ~body status : response =
let respond ?(flush=false) ?headers ?body status : response =
fun wr ->
let headers = Cohttp.Header.add_opt headers "connection" "close" in
match body with
| None ->
let res = Response.make ~status ~encoding:(Cohttp.Transfer.Fixed 0) ~headers () in
let res = Response.make ~status ~flush ~encoding:(Cohttp.Transfer.Fixed 0) ~headers () in
Response.write_header res wr
>>= fun () ->
Response.write_footer res wr
>>= fun () ->
Writer.close wr
| Some body ->
let res = Response.make ~status ~encoding:Cohttp.Transfer.Chunked ~headers () in
let res = Response.make ~status ~flush ~encoding:Cohttp.Transfer.Chunked ~headers () in
Response.write_header res wr
>>= fun () ->
Pipe.iter body ~f:(Response.write_body res wr)
Pipe.iter body ~f:(fun buf ->
Response.write_body res wr buf
>>= fun () ->
(match flush with
| true -> Writer.flushed wr
| false -> return ())
)
>>= fun () ->
Response.write_footer res wr
>>= fun () ->
Writer.close wr

let respond_with_pipe ?headers ?(code=`OK) body =
return (respond ?headers ~body:(Some body) code)
let respond_with_pipe ?flush ?headers ?(code=`OK) body =
return (respond ?flush ?headers ~body code)

let respond_with_string ?headers ?(code=`OK) body =
let respond_with_string ?flush ?headers ?(code=`OK) body =
let body = Pipe.of_list [body] in
return (respond ?headers ~body:(Some body) code)
return (respond ?flush ?headers ~body code)

let respond_with_redirect ?headers uri =
let headers = Cohttp.Header.add_opt headers "location" (Uri.to_string uri) in
return (respond ~flush:false ~headers `Found)

let resolve_local_file ~docroot ~uri =
(* This normalises the Uri and strips out .. characters *)
Expand All @@ -274,13 +287,13 @@ module Server = struct
let error_body_default =
"<html><body><h1>404 Not Found</h1></body></html>"

let respond_with_file ?headers ?error_body filename =
let respond_with_file ?flush ?headers ?error_body filename =
Monitor.try_with ~run:`Now
(fun () ->
Reader.open_file filename
>>= fun rd ->
let body = Reader.pipe rd in
return (respond ?headers ~body:(Some body) `OK)
return (respond ?flush ?headers ~body `OK)
)
>>= function
|Ok res -> return res
Expand Down
16 changes: 12 additions & 4 deletions async/cohttp_async.mli
Expand Up @@ -89,8 +89,6 @@ module Client : sig
Uri.t ->
(Response.t * string Pipe.Reader.t) Deferred.t



(** Send an HTTP request with arbitrary method and string Pipe.Reader.t *)
val call :
?interrupt:unit Deferred.t ->
Expand All @@ -112,8 +110,9 @@ module Server : sig
type response

val respond :
?flush:bool ->
?headers:Cohttp.Header.t ->
body:string Pipe.Reader.t option ->
?body:string Pipe.Reader.t ->
Cohttp.Code.status_code -> response

(** Resolve a URI and a docroot into a concrete local filename. *)
Expand All @@ -122,17 +121,26 @@ module Server : sig
(** Respond with a [string] Pipe that provides the response string Pipe.Reader.t.
@param code Default is HTTP 200 `OK *)
val respond_with_pipe :
?flush:bool ->
?headers:Cohttp.Header.t -> ?code:Cohttp.Code.status_code ->
string Pipe.Reader.t -> response Deferred.t

(** Respond with a static [string] string Pipe.Reader.t
(** Respond with a static [string]
@param code Default is HTTP 200 `OK *)
val respond_with_string :
?flush:bool ->
?headers:Cohttp.Header.t -> ?code:Cohttp.Code.status_code ->
string -> response Deferred.t

(** Respond with a redirect to an absolute [uri]
@param uri Absolute URI to redirect the client to *)
val respond_with_redirect :
?headers:Cohttp.Header.t -> Uri.t -> response Deferred.t


(** Respond with file contents, and [error_string Pipe.Reader.t] if the file isn't found *)
val respond_with_file :
?flush:bool ->
?headers:Cohttp.Header.t -> ?error_body:string ->
string -> response Deferred.t

Expand Down
113 changes: 113 additions & 0 deletions bin/cohttp_server_async.ml
@@ -0,0 +1,113 @@
(*
* Copyright (c) 2013 Anil Madhavapeddy <anil@recoil.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*)

open Core.Std
open Async.Std
open Cohttp_async

let serve_file ~docroot ~uri =
Server.resolve_local_file ~docroot ~uri
|> Server.respond_with_file

(** HTTP handler *)
let rec handler ~info ~docroot ~verbose ~index ~body sock req =
let uri = Cohttp.Request.uri req in
let path = Uri.path uri in
(* Get a canonical filename from the URL and docroot *)
let file_name = Server.resolve_local_file ~docroot ~uri in
Unix.stat file_name
>>= fun stat ->
(* Log the request to the console *)
printf "%s %s %s\n"
(Cohttp.(Code.string_of_method (Request.meth req)))
path
(match verbose with
| true -> Sexp.to_string_hum (Unix.Stats.sexp_of_t stat)
| false -> ""
);
match stat.Unix.Stats.kind with
(* Get a list of current files and map to HTML *)
| `Directory -> begin
(* Check if the index file exists *)
Sys.file_exists (Filename.concat file_name index)
>>= function
| `Yes -> (* Serve the index file directly *)
let uri = Uri.with_path uri (Filename.concat path index) in
Server.respond_with_redirect uri
| `No | `Unknown -> (* Do a directory listing *)
Sys.ls_dir file_name
>>= Deferred.List.map ~f:(fun f ->
let file_name = Filename.concat file_name f in
Unix.stat file_name
>>= fun stat ->
let li l = sprintf "<li><a href=\"%s\">%s</a></li>" (Uri.to_string l) in
let link = Uri.with_path uri (Filename.concat path f) in
match stat.Unix.Stats.kind with
| `Directory -> return (li link (sprintf "<i>%s/</i>" f))
| `File -> return (li link f)
| `Socket|`Block|`Fifo|`Char|`Link -> return (sprintf "<s>%s</s>" f))
(* Concatenate the HTML into a response *)
>>= fun html ->
String.concat ~sep:"\n" html
|> fun contents ->
sprintf "
<html>
<body>
<h2>Directory Listing for %s</h2>
<ul>%s</ul>
<hr>%s
</body>
</html>"
file_name contents info
|> Server.respond_with_string
end
(* Serve the local file contents *)
| `File -> serve_file ~docroot ~uri
(* Any other file type is simply forbidden *)
| `Socket | `Block | `Fifo | `Char | `Link ->
Server.respond_with_string ~code:`Forbidden
"<html><body><h2>Forbidden</h2>
<p>This is not a normal file or directory</p></body></html>"

let start_server docroot port host index verbose () =
printf "Listening for HTTP requests on: %s %d\n" host port;
let info = sprintf "Served by Cohttp/Async listening on %s:%d" host port in
Unix.Inet_addr.of_string_or_getbyname host
>>= fun host ->
let listen_on = Tcp.Where_to_listen.create
~socket_type:Socket.Type.tcp
~address:(`Inet (host,port))
~listening_on:(fun _ -> port)
in
Server.create
~on_handler_error:`Ignore
listen_on
(handler ~info ~docroot ~index ~verbose)
>>= fun _ -> never ()

let _ =
Command.async_basic
~summary:"Serve the local directory contents via HTTP"
Command.Spec.(
empty
+> anon (maybe_with_default "." ("docroot" %: string))
+> flag "-p" (optional_with_default 8080 int) ~doc:"port TCP port to listen on"
+> flag "-s" (optional_with_default "0.0.0.0" string) ~doc:"address IP address to listen on"
+> flag "-i" (optional_with_default "index.html" string) ~doc:"file Name of index file in directory"
+> flag "-v" (optional_with_default false bool) ~doc:" Verbose logging output to console"
) start_server
|> Command.run

0 comments on commit 8b6ab28

Please sign in to comment.