提问人:Atila M. Schrieber 提问时间:9/23/2022 最后编辑:Atila M. Schrieber 更新时间:9/24/2022 访问量:109
Haskell XHTML适用于手动输入的数据,但不适用于从文件中读取的数据
Haskell XHTML works with manually typed data, but not with data read from file
问:
我正在开发一个棋盘游戏,每个团队必须在每回合提交一个订单。为了防止滥用,我正在尝试创建一个登录页面,您可以在其中选择一个团队,提供团队的密码,然后继续下一页。
我正在使用 Haskell,使用此处提供的资源,特别是“获取用户输入”部分。
导入、相关数据/类型和页面功能:
import Network.CGI
import Text.XHtml
data Team = Team
{teamID :: Int,
teamName :: String} deriving Eq
type Lang = Int
type Teams = [Team]
page :: String -> Html -> Html
page t b = header << thetitle << t +++ body << b
我有以下loginPage函数:
loginPage :: Lang -> Teams -> Html
loginPage lang teams = page (["Lépés Bejelentkezés", "Turn Login"] !! lang) $
form ! [method "post"] <<
-- [paragraph << (["Csapat: ", "Team: "] !! lang +++ (select ! [name "teamID"] << teamOpts)), -- Generated version, does not work
{- -}
[paragraph << (["Csapat: ", "Team: "] !! lang +++
(select ! [name "teamID"] <<
[option ! [value "0"] << "Anglia", option ! [value "1"] << "Franciaország"])), --} -- Manually typed version, works perfectly
paragraph << (["Jelszó: ", "Password: "] !! lang +++ password "password"),
submit "" (["Tovább", "Next"] !! lang) ]
where
teamOpts = map (\t -> option ! [value . show $ teamID t] << teamName t) $ teams
注释行使用 teamOpts 生成选项列表,并将它们放在名称为“teamID”的 select 标签中。 在当前未注释的行中,我写了(部分)我在调用 teamOpts 时期望的列表。
在 ghci 中,这两种方法都会生成完全相同的 HTML。然而,当我在我的 Web 服务器(Rocky Linux 上的 Apache)上调用此程序时,我得到以下输出:
- 生成:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
><head
><title
>Lépés Bejelentkezés</t
- 手动编写:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
><head
><title
>Lépés Bejelentkezés</title
></head
><body
><form method="post"
><p
>Csapat: <select name="teamID"
><option value="0"
>Anglia</option
><option value="1"
>Franciaország</option
></select
></p
><p
>Jelszó: <input type="password" name="password" id="password"
/></p
><input type="submit" value="Tovább"
/></form
></body
></html
>
正如你所看到的,生成的版本甚至在完成标题标签之前就终止了。
在命令行(在 Web 服务器上)运行脚本时,我得到了预期的结果(与手动编写相同),带有 CGI 标头:。
我也将其设置为 ,但同样的问题仍然存在。Content-type: text/html; charset=ISO-8859-1
Content-type: text/html; charset=UTF-8
我尝试过的其他事情:
在创建列表之前使用 (teams) 和其他强制评估的方法(通常程序在返回后终止
seq
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/
尝试使用更简单的生成器进行调试:(在 cli 中工作完美,但在加载时不工作,返回高达 </t)
showTeams :: Teams -> Html
showTeams teams = page "Teams" $
paragraph << (concat . map (\t -> teamName t ++ ", ") $ teams)
尝试使用严格版本的 IO 函数(如此处推荐)。在 cli 中工作,但在网络上出现错误。
hGetContents: invalid argument (invalid byte sequence)
确保目录中的所有内容都归 apache:apache 所有
必要的代码(newUnitsPage、cgiMain 和 main):
newUnitsPage :: Lang -> Teams -> Units -> Maybe String -> Maybe String -> Html
newUnitsPage lang teams units tid passwd = page (["Új egységek", "New units"] !! lang) body
where
body = paragraph << "PLACEHOLDER" +++ paragraph << fromJust tid +++ paragraph << fromJust passwd
cgiMain = do
-- General setup
liftIO $ hSetEncoding stdin utf8 -- This doesn't change anything either
-- (What I use) {- -
paths' <- liftIO $ listDirectory "./"
let mapPaths = sort $ filter (=~ "\\.hmap$") paths'
hmap <- liftIO $ getNewestMap mapPaths
let teams = fetchTeams hmap --}
{- For your convenience:
Write the following to a file named "test.hmap":
Team {teamID = 0, teamName = "Anglia"}
Team {teamID = 1, teamName = "Franciaország"}
-}
test <- readFile "test.hmap" -- Pretty sure this is where it all goes wrong, but strict reading (Sysem.IO.Strict) does not fix it
let teams = map (\line -> read line :: Team) . lines $ test
-- Defaults to 0 (Hungarian)
mlang <- getInput "lang"
let lang = maybe 0 (\l -> if l `elem` ["1", "en"] then 1 else 0) mlang
-- All Inputs
-- Authentication
tid <- getInput "teamID"
password <- getInput "password"
newUnitOrders <- getInput "newUnitOrders" -- This is for the next page, not yet implemented, since login doesn't work yet.
-- Number coding for which form to show - method to show certain form based on what inputs exist
let code = fromJust $ foldM (\lastCode (mInput, code) -> if isNothing mInput then Just lastCode else Just code)
0 -- If username / password is not supplied, be on login page
[(tid,1),(password,1), -- If newUnitOrders are not supplied, be on newUnit page
(newUnitOrders,2)] -- Etc.
-- The html output
let pages =
[loginPage lang teams,
-- [showTeams teams,
newUnitsPage lang teams units tid password]
setHeader "Content-type" "text/html; charset=UTF-8" -- Optional
output . renderHtml $ pages !! code
main = runCGI $ handleErrors cgiMain
我在文档中一遍又一遍地检查,没有发现任何迹象表明出了什么问题。
感谢您的帮助!
答:
我能够复制您的版本。检查服务器错误日志,您应该会看到有关 hmap 文件中无效字节序列的注释,内容如下:test.hmap
AH01215: xhtml.cgi: test.hmap: hGetContents: invalid argument (invalid byte sequence): /usr/lib/cgi-bin/xhtml.cgi
问题似乎是 Apache 使用 运行 CGI 脚本,而 Haskell 脚本在从其中一个 hmap 文件中读取 unicode 数据时会在某个随机时间点死亡。使评估更严格可能会导致脚本提前失败,但它不会解决问题。LANG=C
最简单的解决方法可能是添加:
liftIO $ setLocaleEncoding utf8 -- from GHC.IO.Encoding
到函数的顶部。(在我的测试中,没有必要更改编码。cgiMain
stdin
这是我用于测试的脚本的完整版本。如果没有这条线,它会以与您观察到的方式完全相同的方式截断输出;使用该行,它工作正常:liftIO $ setLocaleEncoding utf8
import Control.Monad
import Data.Maybe
import Data.List
import System.Directory
import System.IO
import Network.CGI
import Text.XHtml
import GHC.IO.Encoding
data Team = Team
{teamID :: Int,
teamName :: String} deriving (Read, Eq)
type Lang = Int
type Teams = [Team]
page :: String -> Html -> Html
page t b = header << thetitle << t +++ body << b
loginPage :: Lang -> Teams -> Html
loginPage lang teams = page (["Lépés Bejelentkezés", "Turn Login"] !! lang) $
form ! [method "post"] <<
[paragraph << (["Csapat: ", "Team: "] !! lang +++ (select ! [name "teamID"] << teamOpts)), -- Generated version, does not work
paragraph << (["Jelszó: ", "Password: "] !! lang +++ password "password"),
submit "" (["Tovább", "Next"] !! lang) ]
where
teamOpts = map (\t -> option ! [value . show $ teamID t] << teamName t) $ teams
cgiMain :: CGI CGIResult
cgiMain = do
liftIO $ setLocaleEncoding utf8
paths' <- liftIO $ listDirectory "./"
test <- liftIO $ readFile "test.hmap" -- Pretty sure this is where it all goes wrong, but strict reading (Sysem.IO.Strict) does not fix it
let teams = map (\line -> read line :: Team) . lines $ test
-- Defaults to 0 (Hungarian)
mlang <- getInput "lang"
let lang = maybe 0 (\l -> if l `elem` ["1", "en"] then 1 else 0) mlang
-- All Inputs
-- Authentication
tid <- getInput "teamID"
password <- getInput "password"
newUnitOrders <- getInput "newUnitOrders" -- This is for the next page, not yet implemented, since login doesn't work yet.
-- Number coding for which form to show - method to show certain form based on what inputs exist
let code = fromJust $ foldM (\lastCode (mInput, code) -> if isNothing mInput then Just lastCode else Just code)
0 -- If username / password is not supplied, be on login page
[(tid,1),(password,1), -- If newUnitOrders are not supplied, be on newUnit page
(newUnitOrders,2)] -- Etc.
-- The html output
let pages =
[loginPage lang teams]
-- [showTeams teams,
-- newUnitsPage lang teams units tid password]
setHeader "Content-type" "text/html; charset=UTF-8" -- Optional
output . renderHtml $ pages !! code
main :: IO ()
main = runCGI $ cgiMain
评论