fulmin.client
0.1.3
dotnet add package fulmin.client --version 0.1.3
NuGet\Install-Package fulmin.client -Version 0.1.3
<PackageReference Include="fulmin.client" Version="0.1.3" />
<PackageVersion Include="fulmin.client" Version="0.1.3" />
<PackageReference Include="fulmin.client" />
paket add fulmin.client --version 0.1.3
#r "nuget: fulmin.client, 0.1.3"
#:package fulmin.client@0.1.3
#addin nuget:?package=fulmin.client&version=0.1.3
#tool nuget:?package=fulmin.client&version=0.1.3
Fulmin
Fulmin provides a server and client for the Gemini protocol.
Fulmin.Server
Nuget
Install the nuget in your project:
nuget add package fulmin.server
Usage
The Fulmin.Server.GeminiListener class is an object oriented listener to handle serverside gemini request.
open Fulmin.Server
let client = new GeminiListener(GeminiListenerConfiguration.Default)
client.Start()
task {
use! ctx = client.GetContextAsync()
let! writer = ctx.Success()
do! writer.WriteHeading1Async("Hello world")
}
task {
use! ctx = client.GetContextAsync()
do! ctx.NotFound("Not found")
}
The context returned by GetContextAsync is disposable and should be closed to terminate the connection.
Once acquiered, it's possible to process a request in parallel with other tasks.
The GemWriter class proposes methods to write GemText.
Client certificate
The context ClientCertificate property contains the client certificate if available.
The listener will return a CertificateNotValid response automatically if the certificate has expired.
Configuration
The configuration contains the following parameters:
- Address: the listening ip address. Default is 0.0.0.0 any.
- Port: the listening tcp port. Default is 1965, the default gemini port.
- Certificate: the server certificate
it is possible to specify only specific configuration properties by using GeminiListenerConfiguration.Default as a base:
let config = { GeminiListenerConfiguration.Default with Port = 1966 }
ServerCertificate
By default, fulmin generate a new temporary server certificate for localhost with a duration of 24 hours.
For production use, it is possible to pass directly a X509Certificate2 instance, or to let gemini load it from PEM files:
let config1 = { GeminiListenerConfiguration.Default with
Certificate = X509 myCertificate }
let config2 = { GeminiListenerConfiguration.Default with
Certificate =
File(certPath,
privatekeyPath,
None) }
it is also possible to let gemini generate and renew automatically by specifying a certificate host name and a duration:
let config = { GeminiListenerConfiguration.Default with
Certificate =
File(certPath,
privatekeyPath,
Some {
Host = "mysite.com"
Duration=TimeSpan.FromDays(365)}) }
It will store the certificate and the key in specified files, and renew the certificate once expired.
GemServer
For a more functional approach, Fulmin.Server.GemServer defines composable function to write complete gemini services.
let capsule =
choose [
path "/" >=>
OK [
h1 "Fulmin"
lf
text "Fulmin server is a gemini server in F#"
link "hello" "Hello"
]
path "/hello" >=>
input "What's your name?" (fun name ->
OK [
h1 $"Hello {name}"
lf
link "/" "Home"
link "." "Again"
])
]
let stop = startServer GeminiListenerConfiguration.Default capsule
GemPart
The capsule is build from GemPart, functions that take a GeminiContext and return maybe later a GeminiContext.
They can be used as filters, or responses, and composed together.
for instance path "/" returns the context if the request local path is /. Otherwise it returns nothing.
OK [ ... ] always write a success response with specified lines.
When combined with the >=> kleisly operator, it makes a GemPart that write the lines to the response if the path is /.
The operator choose execute GemParts one after the other until one retuns a value.
OK
The OK GemPart ouput a 20 success response and write all formatted lines as content
input
input check whether there is a query string.
If not, it returns a 10 input response with specified prompt.
If present, it calls the function with the query to get a content to return.
Certificate.require
Certificate.require check whether there is a client certificate.
If not, it returns a 60 certificate required response with specified prompt.
If present, the function is called with the certificate to get a content to return.
path "/admin" >=>
Certificate.require "Please authenticate" (fun cert ->
OK [
h1 $"Welcome {cert.Subject}"
let h = cert.GetCertHash()
text $"Hash: {System.Convert.ToHexStringLower(h)}"
link "/" "Back"
]
)
file
the file function output a gemtext file from disk.
path "/file" >=> file "file.gmi"
Redirects
Redirect.temporary and Redirect.permanent redirect to specified urls:
path "/redirect" >=> Redirect.temporary "test"
OKAsync
OkAsync work like OK except that it accepts a IAsyncEnumerable, and render it line by line.
This can be used for progressive loading or streaming.
Notice that some client load the page completely before starting the rendering. However gemini protocol is line oriented, and many client render lines as they are received.
path "/fizzbuzz" >=> parseInput "Up to ?" parseInt (fun value ->
OKAsync (taskSeq {
h1 "Async FizzBuzz"
for i in 1 .. value do
match i%3, i%5 with
| 0, 0 -> text "FizzBuzz"
| 0, _ -> text "Fizz"
| _, 0 -> text "Buzz"
| _ -> text (string i)
do! Task.Delay(1000)
}))
This example returns the fizzbuzz results line by line with a 1 second interval between them.
regexPath
regexPath filter queries like path, but takes a regular expression. The matched group values are passed
to the specified function:
pathRegex @"/page/(?<id>\d+)/content" (fun values ->
OK [
h1 $"""Page {values["id"]}"""
])
GemText
All gemtext line types can be used with OK and OKAsync.
path "/test" >=>
OK [
h1 "Heading 1"
h2 "Heading 2"
h3 "Heading 3"
text "Some text"
pre' "fsharp" [
"let add x y = x + y"
]
lf // short form for empty line
lst [ "Item1"; "Item2"; "Item3"]
lf
quote "Someone said that in on the internet"
link "/" "home"
]
The pre and pre' function avoid the explicit use of preformatted text toggles. It still possible
to use them (preToggle and preToggle') and emit text line between.
In the same way, lst emit multiple listItem at once.
Fulmin.Client
Fulmin client is a full featured client for the gemini protocol.
task {
// create a client
let client = GeminiClient("gemini://geminiprotocol.net")
// query a specific path
use! response = client.RequestAsync("/")
match response with
| Success r ->
// success ! we can read all line, parsed
for line in r.ReadLinesAsync(true, CancellationToken.None) do
printfn "%A" line
| _ -> printfn "%A" response
}
Client certificate
It is possible to create an send certificate with the request:
task {
let client = GeminiClient("localhost")
let cert = createCertificate(createKey(), "Jérémie")
use! response = client.RequestAsync("/", cert)
//...
}
Certificate management must be currently done by the application.
Redirects
By default, the client auto follows redirects with a maximum of 5. It is possible to change the maximum count while constructing the client.
When the miximum count is reached, an exception occures.
However, by passing -1 as a maximum count, redirects do cause error anymore, but just return the redirect information directly.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- FSharp.Control.TaskSeq (>= 0.4.0)
- FSharp.Core (>= 10.0.100)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.