Haskell XHTML适用于手动输入的数据,但不适用于从文件中读取的数据

Haskell XHTML works with manually typed data, but not with data read from file

提问人:Atila M. Schrieber 提问时间:9/23/2022 最后编辑:Atila M. Schrieber 更新时间:9/24/2022 访问量:109

问:

我正在开发一个棋盘游戏,每个团队必须在每回合提交一个订单。为了防止滥用,我正在尝试创建一个登录页面,您可以在其中选择一个团队,提供团队的密码,然后继续下一页。

我正在使用 Haskell,使用此处提供的资源,特别是“获取用户输入”部分。

相关文档:网络.CGI文本.XHtml

导入、相关数据/类型和页面功能:

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&#233;p&#233;s Bejelentkez&#233;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&#233;p&#233;s Bejelentkez&#233;s</title
    ></head
  ><body
  ><form method="post"
    ><p
      >Csapat: <select name="teamID"
    ><option value="0"
      >Anglia</option
      ><option value="1"
      >Franciaorsz&#225;g</option
      ></select
    ></p
      ><p
      >Jelsz&#243;: <input type="password" name="password" id="password"
     /></p
      ><input type="submit" value="Tov&#225;bb"
       /></form
    ></body
  ></html
>

正如你所看到的,生成的版本甚至在完成标题标签之前就终止了。

在命令行(在 Web 服务器上)运行脚本时,我得到了预期的结果(与手动编写相同),带有 CGI 标头:。 我也将其设置为 ,但同样的问题仍然存在。Content-type: text/html; charset=ISO-8859-1Content-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

我在文档中一遍又一遍地检查,没有发现任何迹象表明出了什么问题。

感谢您的帮助!

HTML Apache Haskell xhtml CGI

评论


答:

0赞 K. A. Buhr 9/24/2022 #1

我能够复制您的版本。检查服务器错误日志,您应该会看到有关 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

到函数的顶部。(在我的测试中,没有必要更改编码。cgiMainstdin

这是我用于测试的脚本的完整版本。如果没有这条线,它会以与您观察到的方式完全相同的方式截断输出;使用该行,它工作正常: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

评论

0赞 Atila M. Schrieber 9/24/2022
谢谢,我的代码现在可以工作了。在我发布问题后,我确实查看了日志,但我必须上路才能更新我的问题。
0赞 Atila M. Schrieber 9/24/2022
(按回车键而不移位)我稍微研究了错误,但我无法确定来源,因为我认为将 Apache 的默认编码和 stdin(在第二 thugt 上,stdout 会更合适)设置为 UTF-8 消除了编码问题。很抱歉没有包括其余的导入,我的导入比我的源文件中使用的要多得多,所以我只放了最相关的导入。感谢您的修复!