Snaplet Request Local State
1. 什么 Snaplet
Snap 从 0.6 版本引入 Snaplet 这个设计,它使得 Web 应用什么可组合化,开发者可以设计许多独立的功能模块,或者说小的应用模块,然后通过组合以组建一个大型的应用。
详细资料请看这里1。
2. Request local state
Request Local State 是 snaplet 的一个设计目标2。 初识时并没引起什么关注,也是不太理解具体含义。 最近在写 Snaplet-OAuth 的时候遇到问题,就是由于不知道这个东西的含义所造成的。
3. Snaplet-oauth-0.0.0
根据 Snaplet 的常规模式,定义一个 data type 用于保存相关信息,比如
data OAuthSnaplet = OAuthSnaplet │ │ │ │ │ { getOauth :: OAuth2 │ │ │ │ │ , getCodeParam :: BS.ByteString │ │ │ │ │ } │ │ │ │ │ class HasOauth b wher e │ oauthLens :: Lens b (Snaplet OAuthSnaplet) │ data OAuth2 = OAuth2 { oauthClientId :: BS.ByteString │ , oauthClientSecret :: BS.ByteString │ , oauthOAuthorizeEndpoint :: BS.ByteString │ , oauthAccessTokenEndpoint :: BS.ByteString │ , oauthCallback :: Maybe BS.ByteString │ , oauthAccessToken :: Maybe BS.ByteString │ } deriving (Show, Eq)
如果从 Monad State Trans 的角度去理解, OAuthSnaplet
就是一个要成为
State 的一个用户类型。 HasOauth
可以理解为用户和其他 Snaplet 组合的接口。
如下代码就展示了如果将 OAuthSnaplet 加入到一个新的应用程序。(其实就是另一个 Snaplet) 如果你已用过其他 Snaplet,这看上去会很熟悉、常规。
│ data App = App │ │ { _weibo :: Snaplet OAuthSnaplet │ │ } │ │ │ makeLens ''App │ │ instance HasOauth App where │ │oauthLens = weibo
拿新浪微薄3举例,OAuth 的验证简单来说就是
- 重定向到新浪微薄 OAuth 的验证页面,让用户授权
- 授权后新浪微薄会调用我们的 App 指定的 Callback URL
- 我们需要实现这个 Callback 已获取最终的 access token
下面来看下这个 callback 的实现
│ │ oauthCallbackHandler :: HasOauth b │ │ │ │ │ │ => Maybe BS.ByteString │ │ │ │ │ │ -> Handler b b () │ oauthCallbackHandler uri = do │ │ oauthSnaplet <- getOauthSnaplet │ │ codeParam <- decodedParam' (getCodeParam oauthSnaplet) │ │ oauth <- getOauth oauthSnaplet │ │ maybeToken <- liftIO $ requestAccessToken oauth codeParam │ │ case maybeToken of │ │ │ Just token -> do │ │ │ │ updateOAuthSnaplet (modify $ modifyOAuthState token) │ │ │ │ redirect $ fromMaybe "/" uri │ │ │ _ -> writeBS "Error getting access token." │ │ │ │ │ │ │ modifyOAuthState :: AccessToken -> OAuthSnaplet -> OAuthSnaplet │ modifyOAuthState (AccessToken at) oa = OAuthSnaplet { getOauth = newOA, getCodeParam = getCodeParam oa } │ │ │ │ │ │ │ │ │ │ │where newOA = originOA { oauthAccessToken = Just at } │ │ │ │ │ │ │ │ │ │ │ │ originOA = getOauth oa │ │ │ │ │ │ │ │ │ │ │ │ │ updateOAuthSnaplet :: (MonadSnaplet m) => m b OAuthSnaplet a -> m b OAuthSnaplet a │ updateOAuthSnaplet = with' oauthLens
这里主要关注的是第 11 行到 13 行, Just token
表示成功获取了
AccessToken,然后要
- 将 OAuthSnpalet 里的 oauth 的 AccessToken 更新掉。
- 然后将更新后 OAuthSnaplet 替代掉原来的
这样一来 OAuthSnaplet 就有 AccessToken,在往后的 Handler 都可以拿到这个 AcceeToken 来访问微薄资源。
然后事实并不是这样子,在这个 oauthCallbackHandler 对 OAuthSnaplet 的更新只限于这个 Handler。
因为 snap 是多线程的且线程安全,每一次的 request 都是对 snaplet 状态的一份新拷贝。 而由于初始化 OauthSnaplet 的时候是没有 AccessToken 的,这就意谓着所有的 Handler 默认读到的 AccessToken 是空的。
4. 如何解决
5. 还有什么问题
你可能已经发现,这样的实现方式,如何支持多用户,以及多个 OAuth Provider 呢? 我还没有答案,如果你知道怎么做,欢迎send Pull Request.