A batteries included HTTP/1.1 client in OCaml
at main 93 lines 2.8 kB view raw
1(*--------------------------------------------------------------------------- 2 Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved. 3 SPDX-License-Identifier: ISC 4 ---------------------------------------------------------------------------*) 5 6let src = Logs.Src.create "requests.method" ~doc:"HTTP Methods" 7 8module Log = (val Logs.src_log src : Logs.LOG) 9 10type t = 11 [ `GET 12 | `POST 13 | `PUT 14 | `DELETE 15 | `HEAD 16 | `OPTIONS 17 | `PATCH 18 | `CONNECT 19 | `TRACE 20 | `Other of string ] 21 22let to_string = function 23 | `GET -> "GET" 24 | `POST -> "POST" 25 | `PUT -> "PUT" 26 | `DELETE -> "DELETE" 27 | `HEAD -> "HEAD" 28 | `OPTIONS -> "OPTIONS" 29 | `PATCH -> "PATCH" 30 | `CONNECT -> "CONNECT" 31 | `TRACE -> "TRACE" 32 | `Other s -> String.uppercase_ascii s 33 34let of_string s = 35 match String.uppercase_ascii s with 36 | "GET" -> `GET 37 | "POST" -> `POST 38 | "PUT" -> `PUT 39 | "DELETE" -> `DELETE 40 | "HEAD" -> `HEAD 41 | "OPTIONS" -> `OPTIONS 42 | "PATCH" -> `PATCH 43 | "CONNECT" -> `CONNECT 44 | "TRACE" -> `TRACE 45 | other -> `Other other 46 47let pp ppf m = Fmt.pf ppf "%s" (to_string m) 48 49let is_safe = function 50 | `GET | `HEAD | `OPTIONS | `TRACE -> true 51 | `POST | `PUT | `DELETE | `PATCH | `CONNECT | `Other _ -> false 52 53let is_idempotent = function 54 | `GET | `HEAD | `PUT | `DELETE | `OPTIONS | `TRACE -> true 55 | `POST | `PATCH | `CONNECT | `Other _ -> false 56 57type body_semantics = Body_required | Body_optional | Body_forbidden 58 59let request_body_semantics = function 60 | `POST | `PUT | `PATCH -> Body_required 61 | `DELETE | `OPTIONS -> Body_optional 62 | `GET -> 63 Body_optional 64 (* RFC 9110 Section 9.3.1: GET body has no defined semantics *) 65 | `HEAD -> 66 Body_forbidden 67 (* RFC 9110 Section 9.3.2: identical to GET but no body in response *) 68 | `TRACE -> Body_forbidden (* RFC 9110 Section 9.3.8: MUST NOT send body *) 69 | `CONNECT -> 70 Body_forbidden (* RFC 9110 Section 9.3.6: no body in CONNECT request *) 71 | `Other _ -> Body_optional (* Unknown methods - allow body for flexibility *) 72 73let has_request_body = function 74 | `POST | `PUT | `PATCH -> true 75 | `GET | `HEAD | `DELETE | `OPTIONS | `CONNECT | `TRACE -> false 76 | `Other _ -> false (* Conservative default for unknown methods *) 77 78let is_cacheable = function 79 | `GET | `HEAD -> true 80 | `POST -> true (* POST can be cacheable with explicit headers *) 81 | `PUT | `DELETE | `PATCH | `OPTIONS | `CONNECT | `TRACE | `Other _ -> false 82 83let equal m1 m2 = 84 match (m1, m2) with 85 | `Other s1, `Other s2 -> 86 String.equal (String.uppercase_ascii s1) (String.uppercase_ascii s2) 87 | m1, m2 -> m1 = m2 88 89let compare m1 m2 = 90 match (m1, m2) with 91 | `Other s1, `Other s2 -> 92 String.compare (String.uppercase_ascii s1) (String.uppercase_ascii s2) 93 | m1, m2 -> Stdlib.compare m1 m2