How would you access configuration from inside the configureServices method in Giraffe-FSharp?
Here's an abridged section from the Giraffe setup created by the SAFE template via dotnet new SAFE -lang F# --server giraffe:
let configureServices (services : IServiceCollection) =
services.AddCors() |> ignore
services.AddGiraffe() |> ignore
// Want to access configuration here.
[<EntryPoint>]
let main _ =
let contentRoot = Directory.GetCurrentDirectory()
let webRoot = Path.Combine(contentRoot, "WebRoot")
WebHostBuilder()
.UseKestrel()
.UseContentRoot(contentRoot)
.UseIISIntegration()
.UseWebRoot(webRoot)
.Configure(Action<IApplicationBuilder> configureApp)
.ConfigureAppConfiguration(Action<WebHostBuilderContext, IConfigurationBuilder> configureAppConfig)
.ConfigureServices(configureServices)
.ConfigureLogging(configureLogging)
.Build()
.Run()
0
Get the service provider from the services collection, then use that to get the configuration:
let serviceProvider = services.BuildServiceProvider()
let config = serviceProvider.GetService<IConfiguration>()
Related
I am using JWT(System.IdentityModel.Tokens.Jwt) for authentication in Giraffe F#. I am successfully getting the JWT token, but when I use it on my API/tables route I get a 401 unauthorized response. I have tried to change the issuer and audience to http://localhost:5001 and http://localhost:5000 respectively that didn't work then I disabled ValidateAudience but didn't work either.
Here is my route:
open System
open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Cors.Infrastructure
open Microsoft.AspNetCore.Hosting
open Microsoft.Extensions.Hosting
open Microsoft.Extensions.Logging
open Microsoft.Extensions.DependencyInjection
open Giraffe
open backend.HttpHandlers
open System.Text
open Microsoft.IdentityModel.Tokens
open Microsoft.AspNetCore.Authentication.JwtBearer
open Microsoft.AspNetCore.Http
open JwtAuth
let authorize : HttpFunc -> HttpContext -> HttpFuncResult =
requiresAuthentication (challenge JwtBearerDefaults.AuthenticationScheme)
let webApp =
choose [ subRoute
"/api"
(choose [ POST
>=> choose [ route "/token" >=> Auth.handlePostToken
route "/user/add" >=> handleAddUser
route "/table/create">=> handleAddTable
route "/table/addData">=> authorize>=> handleAddTableData ]
GET
>=> choose [
route "/tables">=> authorize>=> handleGetTableNames ] ])
setStatusCode 404 >=> text "Not Found" ]
// ---------------------------------
// Error handler
// ---------------------------------
let errorHandler (ex: Exception) (logger: ILogger) =
logger.LogError(ex, "An unhandled exception has occurred while executing the request.")
clearResponse
>=> setStatusCode 500
>=> text ex.Message
Here is my config:
// ---------------------------------
// Config and Main
// ---------------------------------
let configureCors (builder: CorsPolicyBuilder) =
builder
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
|> ignore
let configureApp (app: IApplicationBuilder) =
let env =
app.ApplicationServices.GetService<IWebHostEnvironment>()
(match env.IsDevelopment() with
| true -> app.UseDeveloperExceptionPage()
| false ->
app.UseCors(configureCors)
.UseAuthentication()
.UseGiraffeErrorHandler(errorHandler)
.UseHttpsRedirection())
.UseGiraffe(webApp)
let configureServices (services: IServiceCollection) =
let sp = services.BuildServiceProvider()
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(fun options ->
options.TokenValidationParameters <-
TokenValidationParameters(
ValidateActor = false,
ValidateAudience = false,
ValidateLifetime = false,
ValidateIssuerSigningKey = false,
ValidIssuer = "http://localhost:5001",
ValidAudience = "http://localhost:5000",
IssuerSigningKey = SymmetricSecurityKey(Encoding.UTF8.GetBytes(Auth.secret))
))|> ignore
services.AddCors() |> ignore
services.AddGiraffe() |> ignore
let configureLogging (builder: ILoggingBuilder) =
builder.AddConsole().AddDebug() |> ignore
[<EntryPoint>]
let main args =
Host
.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(fun webHostBuilder ->
webHostBuilder
.Configure(Action<IApplicationBuilder> configureApp)
.ConfigureServices(configureServices)
.ConfigureLogging(configureLogging)
|> ignore)
.Build()
.Run()
0
Here is the function I use to generate the token:
let secret = "spadR2dre#u-ruBrE#TepA&*Uf#U"
let generateToken email =
let claims = [|
Claim(JwtRegisteredClaimNames.Sub, email);
Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) |]
let expires = Nullable(DateTime.UtcNow.AddHours(1.0))
let notBefore = Nullable(DateTime.UtcNow)
let securityKey = SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret))
let signingCredentials = SigningCredentials(key = securityKey, algorithm = SecurityAlgorithms.HmacSha256)
let token =
JwtSecurityToken(
issuer = "http://localhost:5001",
audience = "http://localhost:5000",
claims = claims,
expires = expires,
notBefore = notBefore,
signingCredentials = signingCredentials)
let tokenResult = {
Token = JwtSecurityTokenHandler().WriteToken(token)
}
tokenResult
I am using thunder client vscode to make the requests.
How can I redirect a connection from http to https using Suave?
at https://gist.github.com/ademar/f4ddb788162dbdd9e104574e2accf07f I found this:
let redirectToSsl : WebPart =
context(fun c ->
match c.request.header "x-forwarded-proto" with
| Choice1Of2 "http" ->
let uriBuilder = new UriBuilder(
Scheme = Uri.UriSchemeHttps,
Path = c.request.path,
Host = c.request.host)
Redirection.redirect (uriBuilder.Uri.ToString())
| _ -> fun _ ->async { return None })
but I am not really sure where that would fit in the pipeline?
I would change two things:
Check for the actual protocol. I think that x-forwarded-proto is only used for proxies, but I'm not certain.
To fit it into your pipeline, accept a webpart to invoke when the access is secure.
Result looks like this:
let redirectToSsl allow : WebPart =
context (fun c ->
if c.request.binding.scheme.secure then
allow
else
let uriBuilder =
new UriBuilder(
Scheme = Uri.UriSchemeHttps,
Path = c.request.path,
Host = c.request.host)
Redirection.redirect (uriBuilder.Uri.ToString()))
Usage looks like this:
let app = redirectToSsl Files.browseHome // allow browsing under SSL only
Caveat: I haven't tried this in practice, so there could be other issues I'm overlooking.
module Main
open System
open System.Threading
open System.Threading.Tasks
open NetMQ
open NetMQ.Sockets
let uri = "ipc://hello-world"
let f (token : CancellationToken) =
use server = new ResponseSocket()
use poller = new NetMQPoller()
poller.Add(server)
printfn "Server is binding to: %s" uri
server.Bind(uri)
printfn <| "Done binding."
use __ = server.ReceiveReady.Subscribe(fun x ->
if token.CanBeCanceled then poller.Stop()
)
use __ = server.SendReady.Subscribe(fun x ->
if token.CanBeCanceled then poller.Stop()
)
poller.Run()
printfn "Server closing."
server.Unbind(uri)
let src = new CancellationTokenSource()
let token = src.Token
let task = Task.Run((fun () -> f token), token)
src.CancelAfter(100)
task.Wait() // Does not trigger.
My failed attempt looks something like this. The problem is that the poller will only check the cancellation token if it gets or sends a message. I guess one way to do it would be to send a special cancel message from the client rather than these tokens, but that would not work if the server gets into a send state.
What would be a reliable way of closing the server in NetMQ?
I created a default SAFE app as described here.
Removing redundant stuff, the server is this:
open Giraffe
open Saturn
let webApp = scope {
get "/api/init" (fun next ctx ->
task {
let number = 42
let! counter = task { return number }
return! Successful.OK counter next ctx
})
}
let app = application {
url ("http://0.0.0.0:8085/")
router webApp
memory_cache
use_static "../Client/public"
use_gzip
}
run app
Now, when running app, I see some logging in the console, basically incoming requests:
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 GET http://localhost:8085/api/init
How do I customize the logging? The docs are as scarce as possible, no examples. I need something simple, like logging "going to return 42...".
Or at least some links with cases.
You can pull the fully blown ILogger object from the context, ctx.
Open Microsoft.Extensions.Logging module and then you can do things like this:
let webApp = scope {
get "/api/init" (fun next ctx ->
task {
let logger = ctx.GetLogger();
let number = 42
logger.Log(LogLevel.Information, "Going to return " + number.ToString())
let! counter = task { return number }
return! Successful.OK counter next ctx
})
}
This will bring to your console:
info: object[0]
Going to return 42
I do not have any proper references. I found a similar thing at the Github of Giraffe server for which Saturn is basically a set of abstractions.
Logging configuration is built into v0.9 at least. I used the case below for myself to suppress most of the logging.
open Microsoft.Extensions.Logging
let app = application {
url ("http://0.0.0.0:8085/")
use_router webApp
logging (fun logger -> logger.SetMinimumLevel LogLevel.Critical |> ignore)
}
EDIT for moderators
I had this issue this morning, but the problem has been somehow solved on its own. If it were to come back and I could exactly tell what is happening I would reopen another question with more details.
Thx
I have the following code to start a http listener (I have so far copied and pasted a lot from this series of article )
httpAgent.fs :
namespace Server.Core
open System.Net
open System.Threading
type Agent<'T> = MailboxProcessor<'T>
/// HttpAgent that listens for HTTP requests and handles
/// them using the function provided to the Start method
type HttpAgent private (url, f) as this =
let tokenSource = new CancellationTokenSource()
let agent = Agent.Start((fun _ -> f this), tokenSource.Token)
let server = async {
use listener = new HttpListener()
listener.Prefixes.Add(url)
listener.Start()
while true do
let! context = listener.AsyncGetContext()
agent.Post(context) }
do Async.Start(server, cancellationToken = tokenSource.Token)
/// Asynchronously waits for the next incomming HTTP request
/// The method should only be used from the body of the agent
member x.Receive(?timeout) = agent.Receive(?timeout = timeout)
/// Stops the HTTP server and releases the TCP connection
member x.Stop() = tokenSource.Cancel()
/// Starts new HTTP server on the specified URL. The specified
/// function represents computation running inside the agent.
static member Start(url, f) =
new HttpAgent(url, f)
httpServer.fs :
module httpServer
open Server.Core
let execute = fun ( server : HttpAgent) -> async {
while true do
let! ctx = server.Receive()
ctx.Response.Reply(ctx.Request.InputString) }
This code runs well in a console project (ie: I can access it with a browser, it does find it) :
[<EntryPoint>]
let main argv =
let siteRoot = #"D:\Projects\flaming-octo-spice\src\Site"
let url = "http://localhost:8082/"
let server = HttpAgent.Start(url, httpServer.execute)
printfn "%A" argv
let s = Console.ReadLine()
// Stop the HTTP server and release the port 8082
server.Stop()
0 // return an integer exit code
whereas in my test, I cannot access the server. I have even put some breakpoint in order to check with my browser if the server was up and running , but chrome tells me no host exists with ths url.
namespace UnitTestProject1
open System
open Microsoft.VisualStudio.TestTools.UnitTesting
open Server.Core
open System.Net.Http
[<TestClass>]
type HttpServerTests() =
[<TestMethod>]
member x.Should_start_a_web_site_with_host_address () =
let host = "http://localhost:8082/"
let server = HttpAgent.Start(host, httpServer.execute)
let url = "http://localhost:8082/test/url"
let client = new HttpClient()
let response = client.GetAsync(url)
Assert.IsTrue(response.Result.IsSuccessStatusCode )
Thanks for any enlightment...
You're starting server at port 8092, but client tries to access it at 8082.