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

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

Я использую инструмент сборки 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 .. lame. Импорт 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 и не знаком с Wai, поэтому не знаю, с чего начать. Какие изменения мне нужно внести в пример кода в сообщении в блоге для обслуживания статических файлов?

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

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

Спасибо!


person Ecognium    schedule 24.06.2015    source источник
comment
Для первого, я думаю, вам нужно добавить wai-app-static в build-depends в вашем .cabal файле.   -  person Michael Snoyman    schedule 24.06.2015
comment
Спасибо, Майкл. На самом деле я поместил пакет не в тот файл Кабала. Так что wai-middleware-static работает хорошо. Я играю с кодом из связанного сообщения в блоге и заметил, что он связывает промежуточное ПО, поэтому решил использовать статическое промежуточное ПО. Вероятно, мне понадобится больше времени, чтобы понять, как использовать wai-app-static в этом контексте.   -  person Ecognium    schedule 24.06.2015
comment
@Ecognium Вы видели эту часть слуги? руководство?   -  person Alp Mestanogullari    schedule 25.06.2015
comment
@AlpMestanogullari Да, но я понял только некоторые его части. У меня есть части :Raw и serveDirectory, и поскольку я строил поверх учебника, в котором используется Wai, я попытался использовать его напрямую. Я только что добавил :<|> "static" :> Raw и :<|> serveDirectory "/static" и получаю указанную выше ошибку. Возможно, мне нужно больше разобраться в настройке, чтобы понять, как интегрироваться с Wai. Я попробовал lifting вызвать serveDirectory, но это не помогло. Я пока только догадываюсь :)   -  person Ecognium    schedule 25.06.2015
comment
Можете ли вы добавить минимальный, но полный пример? Сообщение об ошибке предполагает, что для этого потребуется хорошо расположенный lift, но об этом трудно сказать, не имея возможности просмотреть весь код.   -  person Cactus    schedule 26.06.2015
comment
@Ecognium Я думаю, вы просто неправильно используете serveDirectory. Один из Network.Wai.Middleware.Static фактически переведен в надлежащий обработчик запросов, который уже будет обслуживать статические файлы в каком-то каталоге внутри servant. Только не импортируйте Network.Wai.Middleware.Static, а просто напишите serveDirectory / путь / к / вашему / статическому / каталогу. Пока вы импортируете Servant или Servant.Utils.StaticFiles, это будет работать.   -  person Alp Mestanogullari    schedule 26.06.2015
comment
@AlpMestanogullari @Cactus, вот смягченная версия кода Мэтта из его блога. gist.github.com/anonymous/a99bc6b36ac2db64878e. Я объединил все его модули в один файл, удалил все содержимое базы данных, но не очистил расширения / импорт. Когда я запускаю этот код, я получаю указанную выше ошибку несоответствия типа. Обратите внимание, что этот код не использует Network.Wai.Middleware.Static, и я использую квалифицированный импорт Servant StaticFiles.   -  person Ecognium    schedule 26.06.2015
comment
@Ecognium Механизм enter преобразует ваши обработчики из одной монады в другую. Ваш сервер на основе Reader имеет Raw для обслуживания файлов, поэтому enter пытается преобразовать функцию обслуживания файлов с ReaderT ... на EitherT ..., что не сработает, потому что serveDirectory не находится в ReaderT. Я не уверен, является ли это ошибкой в ​​servant или действительно более целесообразно определить обработчик обслуживания файлов отдельно от всех ReaderT. Я уведомил других разработчиков-слуг. А пока.   -  person Alp Mestanogullari    schedule 27.06.2015
comment
@AlpMestanogullari Спасибо. Не могли бы вы сформулировать это как ответ, и я приму его? Я не тратил много времени, пытаясь понять, как был построен весь сервер, а просто сопоставление с образцом на основе того, что сделали другие. До сих пор это работало нормально, но показывает, что мне нужно действительно изучить, как все работает вместе, чтобы вносить изменения в код других людей.   -  person Ecognium    schedule 27.06.2015
comment
@Ecognium Конечно, я постараюсь написать минимальный полный ответ. Если у вас есть дополнительные вопросы, не стесняйтесь заходить на IRC-канал #servant на freenode, я с радостью дам вам еще несколько объяснений в реальном времени.   -  person Alp Mestanogullari    schedule 28.06.2015
comment
Обратите внимание, что теперь это проблема с очень простым решением, которое следует включить в следующем выпуске.   -  person Alp Mestanogullari    schedule 29.07.2015


Ответы (1)


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

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

servant мог бы, возможно, просто оставить 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 = "[email protected]" }
    return person

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

person Alp Mestanogullari    schedule 28.06.2015
comment
Большое спасибо за это! - person runeks; 14.09.2016
comment
Ссылка битая :-( - person Adam Głowacki; 29.03.2021
comment
@ AdamGłowacki После этого ответа все немного изменилось, но это документация, на которую вы захотите взглянуть, чтобы увидеть, как это делается сейчас: docs.servant.dev/en/stable/tutorial/. - person Alp Mestanogullari; 31.03.2021