{"id":18005433,"url":"https://github.com/anuragsoni/shuttle_http","last_synced_at":"2025-03-26T10:32:04.753Z","repository":{"id":40571743,"uuid":"385043195","full_name":"anuragsoni/shuttle_http","owner":"anuragsoni","description":"HTTP/1.1 server and client for ocaml (using Async)","archived":false,"fork":false,"pushed_at":"2024-07-03T00:16:40.000Z","size":881,"stargazers_count":23,"open_issues_count":0,"forks_count":3,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-17T14:52:43.560Z","etag":null,"topics":["async","http","http1-1","http11","ocaml","ssl"],"latest_commit_sha":null,"homepage":"https://anuragsoni.github.io/shuttle_http/","language":"OCaml","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/anuragsoni.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-07-11T20:10:57.000Z","updated_at":"2024-11-04T23:42:47.000Z","dependencies_parsed_at":"2024-10-30T00:46:13.454Z","dependency_job_id":null,"html_url":"https://github.com/anuragsoni/shuttle_http","commit_stats":null,"previous_names":["anuragsoni/shuttle_http","anuragsoni/shuttle"],"tags_count":23,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anuragsoni%2Fshuttle_http","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anuragsoni%2Fshuttle_http/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anuragsoni%2Fshuttle_http/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anuragsoni%2Fshuttle_http/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/anuragsoni","download_url":"https://codeload.github.com/anuragsoni/shuttle_http/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245637056,"owners_count":20648084,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","http","http1-1","http11","ocaml","ssl"],"created_at":"2024-10-30T00:19:45.519Z","updated_at":"2025-03-26T10:32:04.414Z","avatar_url":"https://github.com/anuragsoni.png","language":"OCaml","funding_links":[],"categories":[],"sub_categories":[],"readme":"Shuttle_http is a HTTP/1.1 implementation for OCaml that uses [async](https://opensource.janestreet.com/async/) to provide asynchronous servers and clients.\n\nThis is a relatively low-level library that attempts to provide building blocks for writing http servers and clients. The goal for this library is to be a building block for other libraries and applications.\n\n## Getting Started\n\nYou can install the library using opam.\n\n```sh\nopam install shuttle_http\n```\n\nOnce installed, you'll need to add `shuttle_http` as a dependency in your project's dune file. ex:\n\n```scheme\n(executable\n  (name foo)\n  (libraries shuttle_http))\n```\n\nAPI Documentation can be viewed online on the [OCaml package registry](https://ocaml.org/p/shuttle_http/0.9.1/doc/index.html).\n\n### Getting Started with Servers\n\nShuttle_http is built on top of `Core` and `Async`. Core is intended to be used as a replacement of the OCaml standard library, and Async is a library that implements a non-preemptive user-level threads and provides a high level api for asynchronous execution. The rest of this doc assumed the following modules have been opened:\n\n```ocaml\nopen! Core\nopen! Async\nopen! Shuttle_http\n```\n\n#### Defining a Service\n\nA Service defines how a server responds to incoming requests. It is an asynchronous function that accepts a HTTP request and returns\na deferred HTTP Response.\n\n```ocaml\nlet hello_service (_ : Request.t) =\n  return (Response.create ~body:(Body.string \"Hello World\") `Ok)\n;;\n```\n\nThis service will respond to all requests with a 200 status code and a body with the content \"Hello World\". Shuttle_http will automatically populate the Content-Length header in the response.\n\n#### Running a Server\n\nWe will need to launch a server that will accept `hello_service` and start a running TCP server.\n\n```ocaml\nlet main port =\n  let server =\n    Server.run_inet (Tcp.Where_to_listen.of_port port) (fun _context -\u003e service)\n  in\n  Log.Global.info\n    !\"Server listening on: %s\"\n    (Socket.Address.to_string (Tcp.Server.listening_on_address server));\n  Tcp.Server.close_finished_and_handlers_determined server\n;;\n```\n\nTo launch our server, we will leverage async's `Command.async`, which will use the `main` function we defined, start the Async scheduler before `main` is run, and will stop the scheduler once `main` returns.\n\n```ocaml\nlet () =\n  Command.async\n    ~summary:\"Start an echo server\"\n    (Command.Param.return (fun () -\u003e main 8080))\n  |\u003e Command_unix.run\n;;\n```\n\n#### Echo Server\n\nOur `hello_service` doesn't really do much, we'll now see examples of servers that do a little more work than always responding with the same payload for every request. This example will show how to echo the body received in an incoming request back to the client. We'll also need to do some routing and since `shuttle_http` doesn't ship with a router we'll rely on pattern matching:\n\n```ocaml\nlet websocket_handler =\n  Shuttle_websocket.create (fun ws -\u003e\n    let rd, wr = Websocket.pipes ws in\n    Pipe.transfer_id rd wr)\n;;\n\nlet service request =\n  match Request.path request, Request.meth request with\n  | \"/echo\", `POST -\u003e return (Response.create ~body:(Request.body request) `Ok)\n  | \"/websocket\", `GET -\u003e websocket_handler request\n  | \"/\", `GET -\u003e return (Response.create ~body:(Body.string \"Hello World\") `Ok)\n  | (\"/echo\" | \"/\"), _ -\u003e return (Response.create `Method_not_allowed)\n  | _ -\u003e return (Response.create `Not_found)\n;;\n```\n\nThis is a more involved service, we use pattern matching to dispatch our service on a combination of request path and http method. If we receive a `POST` request on the `/echo` path, we return a new response that uses the same body stream as the incoming request.\nShuttle_http will ensure that the incoming request body is streamed incrementally and echoed back out to the client.\n\n### Getting Started with Clients\n\nWe'll use `httpbin.org` has a target for all the examples related to HTTP clients. We'll need to create a new `address` entity that points to httpbin:\n\n```ocaml\nlet httpbin_address =\n  Client.Address.of_host_and_port (Host_and_port.create ~host:\"httpbin.org\" ~port:443)\n;;\n```\n\nIf the incoming response's body fits entirely in the client's buffer Shuttle_http will represent the body as a fixed sized string, otherwise the body is read as an asynchronous stream so the response can be processed without having to wait for the entire body to arrive over the write.\n\nShuttle_http offers a few different flavors of HTTP clients. The first one we'll see is a OneShot client. OneShot clients open a new TCP\nconnection, send a HTTP Request, wait to receive a Response and then shut-down the TCP connection once the entire response has been consumed.\n\n#### Oneshot clients\n\n```ocaml\nlet one_shot_client () =\n  let%bind response =\n    Client.Oneshot.call\n      ~ssl:(Client.Ssl_config.create ())\n      httpbin_address\n      (Request.create `GET \"/get\")\n  in\n  printf \"Response status: %d\\n\" (Response.status response |\u003e Status.to_int);\n  let%map body = Body.to_string (Response.body response) in\n  print_endline body\n;;\n```\n\nThis client sends a request to `httpbin` using a TLS encrypted connection, and logs the response.\n\n#### Clients supporting keep-alive\n\n```ocaml\nlet persistent_client () =\n  let%bind httpbin =\n    Deferred.Or_error.ok_exn (Client.create ~ssl:(Client.Ssl_config.create ()) httpbin_address)\n  in\n  Monitor.protect\n    ~finally:(fun () -\u003e Client.close httpbin)\n    (fun () -\u003e\n      let%bind response = Client.call httpbin (Request.create `GET \"/stream/20\") in\n      printf !\"Headers: %{sexp: (string * string) list}\" (Response.headers response);\n      let%bind () =\n        Body.Stream.iter_without_pushback\n          (Body.to_stream (Response.body response))\n          ~f:(fun chunk -\u003e printf \"%s\" chunk)\n      in\n      let%bind response = Client.call httpbin (Request.create `GET \"/get\") in\n      printf !\"Headers: %{sexp: (string * string) list}\" (Response.headers response);\n      Body.Stream.iter_without_pushback\n        (Body.to_stream (Response.body response))\n        ~f:(fun chunk -\u003e printf \"%s\" chunk))\n;;\n```\n\nThis example uses a client that supports keep-alive. The client object needs to be forward to every `Client.call` as it maintains internal state to ensure that the same tcp connection will be re-used for multiple requests. The client only send a new request once the previous response has been fully consumed.\n\nPersistent clients are nice as they avoid paying the price of establishing a new TCP connection for subsequent requests. The drawback is that users need to be remember to close the client once they are done with it to avoid leaking file handles. `Monitor.protect` can be a good option\nwhen using persistent clients as it'll provide a consistent cleanup stage via its `finally` callback which can be used to close the client object.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanuragsoni%2Fshuttle_http","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fanuragsoni%2Fshuttle_http","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanuragsoni%2Fshuttle_http/lists"}