Обслуживание статических файлов с помощью Servant / Wai

Я следую этому руководству http://www.parsonsmatt.org/programming/2015/06/07/servant-persistent.html чтобы создавать API через слугу. Я хочу настроить сервер для обслуживания статических файлов, но не смог найти способ сделать это.

Я использую stack инструмент для сборки.

Я модифицировал Main.hs запуск файла для включения static (run port $ static $ logger $ app cfg) и я импортировала Network.Wai.Middleware.Static (static), Я также добавил wai-middleware-static >=0.7.0 && < 0.71 в мой файл клики.

Когда я бегу stack build Я получаю: (Обновление: эта часть полностью моя ошибка. Я добавил пакет в неправильный файл Cabal.. хромой. Импорт Network.Wai.Middleware.Static работает и обслуживает статические файлы. Оставляя ошибку ниже, если кто-то ищет это и находит это полезным.)

Could not find module ‘Network.Wai.Middleware.Static’
Perhaps you meant
  Network.Wai.Middleware.Gzip (from wai-extra-3.0.7.1@waiex_GpotceEdscHD6hq9p0wPOJ)
  Network.Wai.Middleware.Jsonp (from wai-extra-3.0.7.1@waiex_GpotceEdscHD6hq9p0wPOJ)
  Network.Wai.Middleware.Local (from wai-extra-3.0.7.1@waiex_GpotceEdscHD6hq9p0wPOJ)

Затем я попытался использовать слуги serveDirectory следующим образом (упрощенно):

type  API = "users" :> Get   '[JSON]   [Person]
            :<|> "static" :> Raw
server = createPerson :<|> serveDirectory "/static" 

Я получаю эту ошибку:

Couldn't match type ‘IO’ with ‘EitherT ServantErr IO’
arising from a functional dependency between:
  constraint ‘Servant.Server.Internal.Enter.Enter
                (IO Network.Wai.Internal.ResponseReceived)
                (AppM :~> EitherT ServantErr IO)
                (IO Network.Wai.Internal.ResponseReceived)’
    arising from a use of ‘enter’
  instance ‘Servant.Server.Internal.Enter.Enter
              (m a) (m :~> n) (n a)’
    at <no location info>
In the expression: enter (readerToEither cfg) server
In an equation for ‘readerServer’:
    readerServer cfg = enter (readerToEither cfg) server

Я новичок в Haskell, и я не знаком с Ваем, поэтому не знаю, с чего начать. Какие изменения мне нужно сделать, чтобы пример кода в посте блога служил статическим файлам?

Изменить: Поскольку комментарии скрыты от представления по умолчанию, я добавляю свой последний комментарий здесь:

Вот уменьшенная версия кода Мэтта из его блога. Я объединил все его модули в один файл, удалил все содержимое базы данных, но не очистил расширения / импорт. Когда я запускаю этот код, я получаю вышеуказанную ошибку несоответствия типов. Обратите внимание, что этот код не использует Network.Wai.Middleware.Static, и я использую квалифицированный импорт Servant StaticFiles.

Спасибо!

1 ответ

Решение

Как описано в соответствующем разделе учебника слуги, вся сделка с enter чтобы ваши обработчики запросов использовали монаду m (в вашем случае некоторые ReaderT монада) и предоставить способ для преобразования вычислений в m к вычислению в стандарте слугиEitherT ServantErr IO монада.

Проблема здесь в том, что вы определяете кучу обработчиков запросов в ReaderT и дополнительный для обслуживания статических файлов и вызова enter на все это. ReaderT обработчики преобразуются в EitherT ... обработчики просто отлично, но enter пытается преобразовать serveDirectory позвонить с ReaderT ... в EitherT ..., Это, конечно, не произойдет в ближайшее время, так как serveDirectory не вычисление в ReaderT ... начать с!

слуга может просто уйти serveDirectory в одиночку - на данный момент у меня нет определенного мнения о том, следует ли нам это делать или нет, или лучше просто отдельно обработать обработчик файлов, чтобы результат вызова enter на всех других конечных точках. Вот как это будет выглядеть (ищите - NEW, чтобы увидеть изменения):

type PersonAPI = 
    "users" :> Capture "name" String :> Get '[JSON] Person
   -- NEW: removed Raw from here

-- NEW
type WholeAPI = PersonAPI :<|> Raw

type AppM = ReaderT Config (EitherT ServantErr IO)

userAPI :: Proxy PersonAPI
userAPI = Proxy

-- NEW
wholeAPI :: Proxy WholeAPI
wholeAPI = Proxy

-- NEW: changed 'userAPI' to 'wholeAPI'
app :: Config -> Application
app cfg = serve wholeAPI (readerServer cfg)

readerServer :: Config -> Server WholeAPI
readerServer cfg = enter (readerToEither cfg) server
              :<|> S.serveDirectory "/static" -- NEW

readerToEither :: Config -> AppM :~> EitherT ServantErr IO
readerToEither cfg = Nat $ \x -> runReaderT x cfg

server :: ServerT PersonAPI AppM
server = singlePerson

singlePerson :: String -> AppM Person
singlePerson str = do
    let person = Person { name = "Joe", email = "joe@example.com" }
    return person

В любом случае, я довел эту тему до сведения разработчиков других слуг, спасибо! Мы действительно не думали о взаимодействии между enter а также serveDirectory до сих пор (ну, я не сделал).

Другие вопросы по тегам