I am building a simple web server. I want to load a constant string from the filesystem when the application starts, and use this string as a password that will be requested from the user later on.
The token needs only to be read once when the server starts and doesn’t change. So it seems very convenient to do this using a top-level binding. For example, in main.ml i would do:
let password = File.read "./token"
and use password in other parts of the application.
Is a top level binding a good fit for this use case? Or are there any pitfalls to this approach?
Instinctively I can imagine there being problems with evaluation order, if say another variable depends on password perhaps. Generally, are all top level bindings guaranteed to run when the program starts?
Thanks for the response. So if there are multiple top level bindings like let () = ... which all do things with side-effects, is it possible to know in which order they will run (e.g. top to bottom) or is it up to the compiler? Also, how does this work across different .ml files?
Top-level expressions are evaluated in a strictly top-to-bottom order at program initialization time.
When there are multiple modules in your program each module will be initialized in the order in which the modules were linked into the final program. This order is guaranteed to be compatible with the dependency graph between the modules so that if a module A depends on a module B, then B will be initialized before A.
To summarize, things generally work as one would expect and there are no suprises
One needs to work pretty hard to come up with tricky cases. For example:
(* In a.ml *)
let x = ref ...
(* In b.ml *)
let () =
A.x := ... (* initialize A.x *)
(* In c.ml *)
let () =
(* do something with A.x that requires it to be initialized;
will fail if [B] is not linked before [C] *)
Because there is no explicit dependency between C and B, it is not guaranteed that B will be initialized before C. The solution is to add a dummy reference from C to B; but note that this example is artificial and normally it is possible to achieve the same in a simpler way.
I would highly recommend storing the password in an ‘environment file’ and loading it into your web server as an environment variable at start time. E.g.
USER_PASSWORD=whatever
Then have some service runner or shell script which starts your server with the environment variable. E.g. on Linux systems systemd is often used to start services. You can find many examples of systemd service definitions, the key is to use the configuration EnvironmentFile=/path/to/file which will cause systemd to inject all the environment variables from that file into the process’s environment.
Once you have the environment variable you can obviously access it using Sys.getenv.
The reason why I am suggesting this approach over reading the file directly at start time is that reading a file makes you depend on I/O, whereas reading an environment variable eliminates that particular I/O from your critical path of server startup.
I read your post with interest. In many languages / runtimes, there is a thing called ‘resources’. These are typically strings of some sort, though maybe subject to demarshalling from some format like text-mode (or even binary) protobufs, and they can be stored in files, databases, or the environment. Passwords, or templates, are good examples of resources. So are config-files. But also, resources could be strings with positions for interpolating text. The idea being, you can “localize” those resources for versions of the program in different (human) languages, without having to compile the program for each language.
I think there’s support in Python for doing this (I know there is for Java), but I don’t know for sure.
In any case, perhaps there’s some OCaml version of resources ? Or maybe it might be something to build?