来自C#和其他语言,以及F#的新手,并尝试移植我使用OO语言构建的SDK库。 SDK负责首先检索访问令牌,在静态字段上设置,然后设置特定间隔以在令牌到期之前连续刷新令牌。
令牌在Authentication
类上设置为静态字段,并在每次到期之前更新。
然后,SDK中的其他actor会联系到Authentication
类,读取它的static
字段标记,在调用REST端点之前放置在Authorization headers
中。 SDK中的所有参与者在每次调用时都会重复使用相同的令牌,直到它过期并且自动获取更新的一个。
这就是行为,我仍然试图围绕几个概念,但我相信在做这个时学习。
这个F#库将从C#调用,在那里它将通过Credentials开始,然后随后实例化其他类/ actor并在为每个单独的方法传递params时调用它们的方法。那些其他演员将使用这个存储的令牌。
要点基本上在Authentication
中有两个静态字段,并在刷新其中一个静态字段(即令牌)时启用对其他actor的访问。
public class Authentication
{
public static Token token;
public static Credentials credentials;
public static Token RequestToken(Credentials credentials)
{
Authentication.credentials = credentials // cache for subsequent use
// http REST call to access token based on credentials/api keys etc.
// Authentication.token = someAccessTokenObject; // cache result
}
public static Token AddTokenObserver(Credentials credentials)
{
this.RequestToken(credentials);
// set interval, like call RequestToken every 24 hrs
}
}
public class Class1
{
public someReturnObject RequestService1(someParams) {
// accesses Authentication.credentials
// accesses Authentication.token
// places in the authorization headers
// and calls the web service
}
// + several other methods that repeats the same pattern
}
public class Class2
{
public someReturnObject RequestService2(someParams) {
// accesses Authentication.credentials
// accesses Authentication.token
// places in the authorization headers
// and calls the web service
}
// + several other methods that repeats the same pattern
}
使用SDK
// initialize SDK by passing credentials and enable auto refresh
Authentication.AddTokenObserver(someCredentials) // set token observer
Class1 c1 = new Class1();
c1.RequestService1(someObject1); // uses credentials & token from Authentication
Class c2 = new Class2();
c2.RequestService2(someObject2); // uses credentials & token from Authentication
我的F#尝试
type Credentials = {mutable clientId: string; mutable clientSecret: string;}
type Token = {mutable access_token: string; mutable refresh_token: string}
type Authentication =
static member token = {access_token = ""; refresh_token = ""};
static member credentials = {clientId = ""; clientSecret = "";}
new() = {}
member this.RequestToken(credentials) =
let data : byte[] = System.Text.Encoding.ASCII.GetBytes("");
let host = "https://example.com";
let url = sprintf "%s&client_id=%s&client_secret=%s" host credentials.clientId credentials.clientSecret
let request = WebRequest.Create(url) :?> HttpWebRequest
request.Method <- "POST"
request.ContentType <- "application/x-www-form-urlencoded"
request.Accept <- "application/json;charset=UTF-8"
request.ContentLength <- (int64)data.Length
use requestStream = request.GetRequestStream()
requestStream.Write(data, 0, (data.Length))
requestStream.Flush()
requestStream.Close()
let response = request.GetResponse() :?> HttpWebResponse
use reader = new StreamReader(response.GetResponseStream())
let output = reader.ReadToEnd()
printf "%A" response.StatusCode // if response.StatusCode = HttpStatusCode.OK throws an error
Authentication.credentials.clientId <- credentials.clientId
let t = JsonConvert.DeserializeObject<Token>(output)
Authentication.token.access_token <- t.access_token
Authentication.token.token_type <- t.token_type
reader.Close()
response.Close()
request.Abort()
F#测试
[<TestMethod>]
member this.TestCredentials() =
let credentials = {
clientId = "some client id";
clientSecret = "some client secret";
}
let authenticaiton = new Authentication()
try
authenticaiton.RequestToken(credentials)
printfn "%s, %s" credentials.clientId Authentication.credentials.clientId // Authentication.credentials.clientId is empty string
Assert.IsTrue(credentials.clientId = Authentication.credentials.clientId) // fails
with
| :? WebException -> printfn "error";
问题
在上述单元测试中
Authentication.credentials.clientId is empty string
Assert fails
在调用令牌服务后,我无法在单元测试中访问静态成员。我如何一起接近这一点是有问题的。
我需要帮助在一些F#代码的帮助下翻译F#中的C#行为。我已经构建了Authentication类,并且在实现中存在一些问题,特别是在静态成员周围并随后访问它们。另外,我想遵循函数式编程的规则,并学习如何在F#中的Functional World中完成它。请帮我翻译F#代码中的这种行为。
答案 0 :(得分:0)
解决这个问题的惯用功能方法是首先尝试摆脱全局状态。
有几种方法,但我认为最有效的方法是提供一个AuthenticationContext
,其中包含C#代码保持全局状态的数据,并且每次调用 migth 续订凭据,将结果与可能更新的授权上下文一起返回。
基本上,给定一种使用令牌进行API调用的方法
type MakeApiCall<'Result> = Token -> 'Result
我们想要创建这样的东西:
type AuthenticatedCall<'Result> = AuthenticationContext -> 'Result * AuthenticationContext
然后,您还可以让上下文跟踪是否需要续订(例如,通过存储上次续订时的时间戳,存储到期日期或其他内容),并提供两个功能
type NeedsRenewal = AuthenticationContext -> bool
type Renew = AuthenticationContext -> AuthenticationContext
现在,如果您获得带有功能的凭证
type GetAccessToken = AuthenticationContext -> Token * AuthenticationContext
您可以通过检查凭据是否需要续订来启动该方法的实施,如果是,则在返回之前更新它们。
因此,示例实现可能如下所示:
type AuthenticationContext = {
credentials : Credentials
token : Token
expiryDate : DateTimeOffset
}
let needsRenewal context =
context.expiryDate > DateTimeOffset.UtcNow.AddMinutes(-5) // add some safety margin
let renew context =
let token = getNewToken context.Credentials
let expiryDate = DateTimeOffset.UtcNow.AddDays(1)
{ context with token = token, expiryDate = expiryDate }
let getAccessToken context =
let context' =
if needsRenewal context
then renew context
else context
return context'.token, context'
let makeAuthenticatedCall context makeApicall =
let token, context' = getAccessToken context
let result = makeApiCall token
result, context'
现在,如果您每次拨打API电话,都可以访问上次通话中的AuthenticationContext
,那么基础设施将负责为您续订令牌。
你会很快注意到这只会将问题推到跟踪身份验证上下文,并且你必须经常传递它。例如,如果要进行两次连续的API调用,则执行以下操作:
let context = getInitialContext ()
let resultA, context' = makeFirstCall context
let resultB, context'' = makeSecondCall context'
如果我们能够构建能够跟踪我们上下文的内容,那么我们不必传递它吗?