Hi !
I encounter some difficulties trying to articulate well client and server code in eliom. I fail registering a Post service to an Eliom app and I do not figure why.
Here some code that I tried, as an example to start the discussion :
open%shared Eliom_content.Html.D
let%server service = Eliom_service.create
~path:(Eliom_service.Path ["upload"])
~meth:(Eliom_service.Get Eliom_parameter.unit)
()
let%client service = ~%service
let%shared name () = "demo-upload"
let%shared page_class = "dg-page-demo-upload"
let%server upload_service = Eliom_service.create
~path:(Eliom_service.Path ["upload"])
~meth:(Eliom_service.Post (Eliom_parameter.unit,
Eliom_parameter.file "file"))
()
let%client upload_service = ~%upload_service
let%shared () =
Docgenius_base.App.register
~service:upload_service
(fun () file ->
let () = [%server ( some_code_that_does_smth_server_side : unit ) ] in
Lwt.return
(html
(head (title (txt "Upload")) [])
(body [h1 [txt "ok"]])))
let%shared page () =
let f =
(Form.post_form upload_service
(fun file ->
[p [Form.file_input ~name:file ();
br ();
Form.input ~input_type:`Submit ~value:"Send" Form.string
]]) ()) in
Lwt.return
[ h1 [txt "Upload"]
; p [txt "form upload"]
; f
]
Above code is included in ocsigen-start as a demo page, and leads to following error message :
File "demo_upload.eliom", line 28, characters 19-25:
Error: Uninterpreted extension 'server'.
I know from https://ocsigen.org/tuto/6.4/manual/misc that I can create a post service with Eliom_service.create and then register an action. This is fine, but I cannot figure out how to notify back easily that a server side computation were fine (the document were properly uploaded), or notify back a result. Maybe should I register an ocaml handler instead of an action for this purpose ?
And my file is called demo_upload.eliom.
Also, if I comment lines from “let () = [%server” to “: unit ) ] in”, the project compiles and builds up.
My complete error is indeed:
Oh yes sorry I didn’t see the [%server ] part.
This does not exist.
If you have a function declared in shared section and want to have a different behaviour on server and client, you can call another function with different implementations on both sides.
If you expect the [%server ] section to be executed on the server even if your page is generated on the client, you need to define a server function.
Be careful that the call to the server function will take time.
You may want to do the call asynchronously, for example by displaying some part of the page with Ot_spinner (see example in Ocsigen Start).
Thanks a lot for your help Vincent ! Now it works for me
As I had some difficulties implementing the code, I post below my reasoning and final code (do not hesitate to criticize it):
As I wanted to upload, I had to use Eliom_request_info.get_all_files (but maybe someone as a more straightforward way to do so ?). Indeed param cannot contain a file (a value of type Ocsigen_extensions.file_info) because
my_type is the type of param and must be derivable into json
type json_file = Ocsigen_extensions.file_info [@@deriving json] is not legal code
Here is the code I came with:
let%server upload_handler () =
match Eliom_request_info.get_all_files () with
| None -> Lwt.return ()
| Some l -> let file = snd (List.hd l) in
let newname = "path_to/thefile" in
(try
Unix.unlink newname;
with _ -> ());
let tmp_filename = Eliom_request_info.get_tmp_filename file in
Lwt_unix.write_string
Lwt_unix.stdout
("tmp: "^(tmp_filename))
0
(String.length tmp_filename)
;
Lwt_unix.link tmp_filename newname;
Lwt_unix.system ("cp "^newname^" new_path/");
Lwt.return ()
let%client upload_handler () =
~%(Eliom_client.server_function
[%derive.json: unit]
(Os_session.connected_wrapper upload_handler))
()
let%shared () =
Docgenius_base.App.register
~service:upload_service
(fun () file ->
let%lwt () = upload_handler () in
Lwt.return
(html
(head (title (txt "Upload")) [])
(body [h1 [txt "ok"]])))
I don’t understand how your code could work, as your server function is implemented as a service and this service can access only its own files.
Your service is used to upload a file. It makes few sense to have a client side implementation.
Type file contains the information about uploaded file.
May be we could invent some kind of client side counterpart that could be sent to the server transparently but it does not exist in Eliom for now.
What you probably want is to send your file asynchronously to the server, without stopping the client process. For security reasons browsers do not allow to send a file through a server function. But you can bypass this limitation by creating a server side service dedicated to the upload, that will return nothing (or a boolean for example). This service will not generated any page.
You can find an example here: http://ocsigen.org/tuto/6.4/manual/how-to-send-a-file-to-server-without-stopping-the-client-process.html
This example uses “OCaml services” (services returning OCaml values) which is the low level feature behind server functions.
Thanks for your advice and both links !
Unfortunately, it seems that the first tutorial is broken, as the code leads to this error message I do not understand:
File "upload_pic.eliom", line 17, characters 30-36:
Error: This expression has type
?use_capture:bool ->
#Js_of_ocaml.Dom_html.eventTarget Js_of_ocaml.Js.t ->
Js_of_ocaml.Dom_html.event Js_of_ocaml.Js.t Lwt.t
but an expression was expected of type
([< Html_types.input ] as 'a) Eliom_content.Html.To_dom.elt =
'a Eliom_content.Html.elt
Makefile.os:284: recipe for target '_client/upload_pi.cmo' failed
make: *** [_client/upload_pi.cmo] Error 2
error matches “submit” of line “clicks (To_dom.of_input submit)”.
The second resource is a bit difficult to me right now, I imagine I can get inspired with demo_notif.eliom in ocsigen_start.
By the way, I think I can use an action (with NoReload, or a Unit), and maybe I can get feed-back from the server with a notification, when I learn to use it.
Thanks a lot for your help Vincent. Thanks to you I made good progresses in learning this framework. I hope I will be able to fully use it soon
Have a nice week-end !