Introduce client & parser, switch to Dune
- Server and client now have dedicated module files - Menu and Item have their own module files - Wrote parsers for Menu and Item - Switch from jbuilder to dune - Introducing gopher client - IPv6
This commit is contained in:
parent
48a39936ff
commit
7b1ec728cf
9 changed files with 142 additions and 84 deletions
2
dune-project
Normal file
2
dune-project
Normal file
|
@ -0,0 +1,2 @@
|
|||
(lang dune 1.3)
|
||||
(name gopher)
|
|
@ -1,16 +1,17 @@
|
|||
opam-version: "1.2"
|
||||
name: "gopher"
|
||||
version: "0.1.0"
|
||||
homepage: "https://cgit.orbitalfox.eu/ocaml-gopher/about"
|
||||
synopsis: "OCaml library for creating Gopher applications"
|
||||
homepage: "https://cgit.orbitalfox.eu/ocaml-gopher"
|
||||
doc: "README.md"
|
||||
dev-repo: "git://orbitalfox.eu/ocaml-gopher"
|
||||
bug-reports: "mailto:sp@orbitalfox.eu"
|
||||
maintainer: "Stavros Polymenis <sp@orbitalfox.eu>"
|
||||
authors: "Stavros Polymenis <sp@orbitalfox.eu>"
|
||||
license: "EUPL"
|
||||
build: [
|
||||
["jbuilder" "build" "--root" "." "-j" jobs "@install"]
|
||||
["dune" "build" "--root" "." "-j" jobs "@install"]
|
||||
]
|
||||
depends: [
|
||||
"jbuilder" {build}
|
||||
"dune" {build}
|
||||
"lwt"
|
||||
]
|
||||
|
|
5
src/dune
Normal file
5
src/dune
Normal file
|
@ -0,0 +1,5 @@
|
|||
(library
|
||||
(name gopher)
|
||||
(public_name gopher)
|
||||
(modules menu item lwt_server lwt_client)
|
||||
(libraries lwt lwt.unix))
|
|
@ -1,74 +0,0 @@
|
|||
module Item = struct
|
||||
type t =
|
||||
| Text_file
|
||||
| Submenu
|
||||
| CCSO_nameserver
|
||||
| Error
|
||||
| Binhexencoded_file
|
||||
| DOS_file
|
||||
| Uuencoded_file
|
||||
| Text_search
|
||||
| Telnet
|
||||
| Binary_file
|
||||
| Alternate_server
|
||||
| GIF_file
|
||||
| Image_file
|
||||
| Telnet_3270
|
||||
| HTML_file
|
||||
| Informational_message
|
||||
| Sound_file
|
||||
|
||||
let char = function
|
||||
| Text_file -> '0'
|
||||
| Submenu -> '1'
|
||||
| CCSO_nameserver -> '2'
|
||||
| Error -> '3'
|
||||
| Binhexencoded_file -> '4'
|
||||
| DOS_file -> '5'
|
||||
| Uuencoded_file -> '6'
|
||||
| Text_search -> '7'
|
||||
| Telnet -> '8'
|
||||
| Binary_file -> '9'
|
||||
| Alternate_server -> '+'
|
||||
| GIF_file -> 'g'
|
||||
| Image_file -> 'I'
|
||||
| Telnet_3270 -> 'T'
|
||||
| HTML_file -> 'h'
|
||||
| Informational_message -> 'i'
|
||||
| Sound_file -> 's'
|
||||
|
||||
let v item user_display selector hostname port =
|
||||
let (<+>) a b = a ^ "\t" ^ b in
|
||||
(Char.escaped (char item)) ^ user_display <+> selector <+> hostname <+> string_of_int port <+> "\r\n"
|
||||
end
|
||||
|
||||
module Menu = struct
|
||||
let ending = "\n."
|
||||
end
|
||||
|
||||
module Lwt = struct
|
||||
let apply handler (file_descr, socket) =
|
||||
let open Lwt.Infix in
|
||||
let buf = Bytes.create 256 in
|
||||
Lwt_unix.read file_descr buf 0 (Bytes.length buf)
|
||||
>>= (fun x ->
|
||||
let response = handler @@ Bytes.(to_string (sub buf 0 x)) in
|
||||
Lwt_unix.write_string file_descr response 0 (String.length response)
|
||||
)
|
||||
>>= (fun x -> Lwt_unix.(shutdown file_descr SHUTDOWN_ALL); Lwt.return_unit)
|
||||
|
||||
let rec accept_all handler socket () =
|
||||
let open Lwt.Infix in
|
||||
Lwt_unix.accept socket
|
||||
>>= (fun pair -> Lwt.catch (fun () -> apply handler pair) (fun exn -> prerr_endline (Printexc.to_string exn); Lwt.return_unit))
|
||||
>>= accept_all handler socket
|
||||
|
||||
let gopherd ?(host="") ?(port=70) ?(request_queue_size=128) handler =
|
||||
let open Lwt.Infix in
|
||||
let socket = Lwt_unix.(socket PF_INET6 SOCK_STREAM 0) in
|
||||
Lwt_unix.setsockopt socket SO_REUSEADDR true;
|
||||
let host = if host = "" then Unix.inet6_addr_any else Unix.inet_addr_of_string host in
|
||||
let bind = Lwt_unix.bind socket Unix.(ADDR_INET (host, port)) in
|
||||
Lwt_unix.listen socket request_queue_size;
|
||||
Lwt_main.run (bind >>= accept_all handler socket)
|
||||
end
|
80
src/item.ml
Normal file
80
src/item.ml
Normal file
|
@ -0,0 +1,80 @@
|
|||
type item_t =
|
||||
| Text_file
|
||||
| Submenu
|
||||
| CCSO_nameserver
|
||||
| Error
|
||||
| Binhexencoded_file
|
||||
| DOS_file
|
||||
| Uuencoded_file
|
||||
| Text_search
|
||||
| Telnet
|
||||
| Binary_file
|
||||
| Alternate_server
|
||||
| GIF_file
|
||||
| Image_file
|
||||
| Telnet_3270
|
||||
| HTML_file
|
||||
| Informational_message
|
||||
| Sound_file
|
||||
| Unknown
|
||||
|
||||
type t = { item_type : item_t; display_string : string; selector : string; hostname : string; port : int }
|
||||
|
||||
let char_of_type = function
|
||||
| Text_file -> '0'
|
||||
| Submenu -> '1'
|
||||
| CCSO_nameserver -> '2'
|
||||
| Error -> '3'
|
||||
| Binhexencoded_file -> '4'
|
||||
| DOS_file -> '5'
|
||||
| Uuencoded_file -> '6'
|
||||
| Text_search -> '7'
|
||||
| Telnet -> '8'
|
||||
| Binary_file -> '9'
|
||||
| Alternate_server -> '+'
|
||||
| GIF_file -> 'g'
|
||||
| Image_file -> 'I'
|
||||
| Telnet_3270 -> 'T'
|
||||
| HTML_file -> 'h'
|
||||
| Informational_message -> 'i'
|
||||
| Sound_file -> 's'
|
||||
| Unknown -> ' '
|
||||
|
||||
let type_of_char = function
|
||||
| '0' -> Text_file
|
||||
| '1' -> Submenu
|
||||
| '2' -> CCSO_nameserver
|
||||
| '3' -> Error
|
||||
| '4' -> Binhexencoded_file
|
||||
| '5' -> DOS_file
|
||||
| '6' -> Uuencoded_file
|
||||
| '7' -> Text_search
|
||||
| '8' -> Telnet
|
||||
| '9' -> Binary_file
|
||||
| '+' -> Alternate_server
|
||||
| 'g' -> GIF_file
|
||||
| 'I' -> Image_file
|
||||
| 'T' -> Telnet_3270
|
||||
| 'h' -> HTML_file
|
||||
| 'i' -> Informational_message
|
||||
| 's' -> Sound_file
|
||||
| _ -> Unknown
|
||||
|
||||
let line item_type user_display selector hostname port =
|
||||
let (<+>) a b = a ^ "\t" ^ b in
|
||||
(Char.escaped (char_of_type item_type)) ^ user_display <+> selector <+> hostname <+> string_of_int port <+> "\r\n"
|
||||
|
||||
let empty = { item_type = Unknown; display_string = ""; selector = ""; hostname = ""; port = 0 }
|
||||
|
||||
let of_line line =
|
||||
let item_type = try type_of_char @@ String.get line 0 with _ -> Unknown in
|
||||
if item_type = Unknown then empty
|
||||
else (
|
||||
let fields = String.split_on_char '\t' (String.sub line 1 (String.length line - 1)) in
|
||||
let nth n = match List.nth_opt fields n with Some v -> v | None -> "" in
|
||||
let port = try int_of_string (nth 3) with Failure _ -> 0 in
|
||||
{ item_type; display_string = nth 0; selector = nth 1; hostname = nth 2; port }
|
||||
)
|
||||
|
||||
let v item_type display_string selector hostname port =
|
||||
{ item_type; display_string; selector; hostname; port }
|
|
@ -1,6 +0,0 @@
|
|||
(jbuild_version 1)
|
||||
|
||||
(library
|
||||
((name gopher)
|
||||
(public_name gopher)
|
||||
(libraries (lwt lwt.unix))))
|
21
src/lwt_client.ml
Normal file
21
src/lwt_client.ml
Normal file
|
@ -0,0 +1,21 @@
|
|||
open Lwt.Infix
|
||||
|
||||
let session socket path () =
|
||||
let rec recv_all reply_bytes () =
|
||||
let recv_bytes = Bytes.create 4096 in
|
||||
Lwt_unix.recv socket recv_bytes 0 (Bytes.length recv_bytes) []
|
||||
>>= function
|
||||
| 0 -> Lwt.return (Bytes.to_string reply_bytes)
|
||||
| _ -> recv_all (Bytes.cat reply_bytes recv_bytes) ()
|
||||
in
|
||||
let path_bytes = Bytes.of_string path in
|
||||
Lwt_unix.send socket path_bytes 0 (Bytes.length path_bytes) []
|
||||
>>= function
|
||||
| -1 | 0 -> Lwt_result.fail "Couldn't send"
|
||||
| _ -> Lwt_result.ok (recv_all (Bytes.create 0) ())
|
||||
|
||||
let gopher ?(port=70) host path =
|
||||
let socket = Lwt_unix.(socket PF_INET SOCK_STREAM 0) in
|
||||
let host = Unix.inet_addr_of_string host in
|
||||
let connection = Lwt_unix.connect socket Unix.(ADDR_INET (host, port)) in
|
||||
Lwt_main.run (connection >>= session socket path)
|
24
src/lwt_server.ml
Normal file
24
src/lwt_server.ml
Normal file
|
@ -0,0 +1,24 @@
|
|||
let apply handler (file_descr, _socket) =
|
||||
let open Lwt.Infix in
|
||||
let buf = Bytes.create 256 in
|
||||
Lwt_unix.read file_descr buf 0 (Bytes.length buf)
|
||||
>>= (fun x ->
|
||||
let response = handler @@ Bytes.(to_string (sub buf 0 x)) in
|
||||
Lwt_unix.write_string file_descr response 0 (String.length response)
|
||||
)
|
||||
>>= (fun _ -> Lwt_unix.(shutdown file_descr SHUTDOWN_ALL); Lwt.return_unit)
|
||||
|
||||
let rec accept_all handler socket () =
|
||||
let open Lwt.Infix in
|
||||
Lwt_unix.accept socket
|
||||
>>= (fun pair -> Lwt.catch (fun () -> apply handler pair) (fun exn -> prerr_endline (Printexc.to_string exn); Lwt.return_unit))
|
||||
>>= accept_all handler socket
|
||||
|
||||
let gopherd ?(host="") ?(port=70) ?(request_queue_size=128) handler =
|
||||
let open Lwt.Infix in
|
||||
let socket = Lwt_unix.(socket PF_INET6 SOCK_STREAM 0) in
|
||||
Lwt_unix.setsockopt socket SO_REUSEADDR true;
|
||||
let host = if host = "" then Unix.inet6_addr_any else Unix.inet_addr_of_string host in
|
||||
let bind = Lwt_unix.bind socket Unix.(ADDR_INET (host, port)) in
|
||||
Lwt_unix.listen socket request_queue_size;
|
||||
Lwt_main.run (bind >>= accept_all handler socket)
|
5
src/menu.ml
Normal file
5
src/menu.ml
Normal file
|
@ -0,0 +1,5 @@
|
|||
let ending = "\n."
|
||||
|
||||
let of_string menu =
|
||||
let lines = String.split_on_char '\n' menu in
|
||||
List.map Item.of_line lines
|
Loading…
Reference in a new issue