一个充分利用所有方面的应用程序的好例子福吉特图书馆的是一个万维网客户端。它显示嵌入图像和GUI元素的超文本文档(到实施填表)。这些文件是从通过ftp、nntp等协议在互联网上提供信息源,gopher和http。在本节中,我们将了解这样一个应用程序是如何实现的在Haskell的Fudget库上实现。一个实际的这个名为wwbrowser的实现主要在夏季完成1994年。1997年夏季进行了一些更新和改进窗口快照如所示图85.
图85.wwbrowser,一个简单的web浏览器,使用它支持内联图像和表单。
1994年版本的wwbrowser具有以下特性:
- 它接受了大部分HTML2.0(在1994年),包括
- 填写表格。
- 内联图像(文件格式:gif、jpeg、xbm、pnm)。
- 它支持常见的协议:http,gopher,ftp,news,本地文件/目录。
- 它并行地获取多个内联图像。这使得wwBrowser比Mosaic更快(1994年使用最广泛的浏览器)获取包含许多小图像的页面时。
- 它简化了url的复制和粘贴。你可以标记一个网址吗例如,在文本编辑器中,然后用鼠标中键单击按钮查看该页面。
关于1994年实施的一些简要事实:- HTML输入被解析为一个抽象语法树,它然后分几个阶段转换为绘图命令使用适当的布局、字体和其他属性。这是用fudget内核完成的(参见第22.1.2条)专门为此写的。
- HTML解析器是使用相当高效的,由Niklas Röjemo开发的回溯解析组合子[罗j95a]在他的Haskell编译器中使用。提高对坏的容忍度HTML,在词法之间添加了一个额外的标记平衡传递分析器和解析器,但解析器在某些web上仍然失败页。
- 形式元素和图像被当作普通的傻瓜来实现,恰当地放在文本中。(它们的大小影响布局。)
- 图像转换(例如,解压缩gif和jpeg)由调用外部程序(giftopm等)。
- 图像处理(颜色重映射和可选的抖动)是在哈斯凯尔完成的。
- 项目规模:大约4000行Haskell。
- 实施时间:约1人月。
1997年修改并增加了以下内容:- 源代码从Haskell 1.2翻译成Haskell 1.4。
- 解析器是使用Swierstra&Duponcheel的确定性、纠错解析组合子[SD96型]. 这个解析器原来是更优雅(可以删除额外的标记平衡过程)和更能容忍糟糕的HTML。同时,解析器已更新以接受大部分HTML3.2[英语97]. 新的解析器的运行速度与旧解析器大致相同。
- 渲染是通过将HTML转换为
绘图
(参见第27.4条),使用类型的函数Html->绘图。。。
(粗略地)。老裁缝做的软糖果仁被扔了出去。福吉特图书馆管理员表格
用于添加支持表格(表格单元格属性行间距
和科尔斯潘
尚不支持)。 - fudget布局系统得到了改进完成段落填充并根据窗口的宽度(请参见第27.6条).
- 对背景颜色和背景图像的支持补充。
- 增加了对通过代理获取文档的支持。(一)代理是一个转发文档请求的服务器。大多数WWW浏览器可以配置为通过代理获取所有文档而不是直接从具有文件。)
- 图像获取、转换和处理被移动到单独的进程,允许显示页面的文本以前的点击和加载。当图像变得众所周知。(与fudgets的并行实现,你可以免费得到这个。)
- 实验支持福普莱特(付国家applets公司)已添加。fupplet是用Haskell编写的applet,使用福吉特图书馆。
- WWWBrowser现在可以读取Netscape创建的书签文件[上网本]并在分层菜单中显示。
- 程序大小现在大约是4500行。
wwbrowser以直接的方式实现。关键数据类型是,毫不奇怪,统一资源定位地址
和Html格式
。这些类型的关键操作是:
数据URL=。。。数据Html=。。。parseURL::String->可能的URLshowURL::URL->StringjoinURL::URL->URL->URLparseHtml::String->任意一个ErrorInfo HtmldrawHtmlDoc::URL->Html->HtmlDrawing类型HtmlDrawing=绘图。。。--详细信息第32.3条
文档是通过胡说八道urlFetchF
,
urlFetchF::F HttpRequest HttpResponse--详细信息第32.2条
数据HttpRequest=HttpReq{reqURL::URL,…}数据HttpResponse=HttpResp{respBody::String,…}
笨蛋urlFetchF
除了处理HTTP协议,但由于HTTP是WWW,它是第一个被实现的。无稽之谈对于其他协议,则使用相同的接口实现。文件由福吉特展示HTML显示
,
htmlDisplayF::F(URL,Html)HttpRequest
它显示在输入和输出上接收到的HTML文档当用户单击中的链接时请求新文档正在显示的文档。并非WWW上的所有文档都是HTML文档。其他类型文档(例如纯文本、gopher页面、ftp目录列表和Usenet新闻文章)通过转换将它们转换为HTML:
toHtml::(URL,HttpResponse)->(URL,Html)
功能toHtml
使用解析HTML
以及其他解析器。使用上述组件,我们可以创建一个简单的web浏览器
简单浏览器=循环(htmlDisplayF>==<mapF toHtml>==<urlFetchF)
但是除了HTML显示之外,WWWBrowser还提供了后退/前进按钮,网址输入栏,历史窗口,书签菜单,文档源窗口以及一个进度报告字段。主楼的结构如所示图86使用布局指定名称布局(请参见第11.2条).WWW浏览器=httpMsgDispF>==<循环hrightf urlFetchF'mainGuiF>==<菜单哪里mainGuiF=urlInputF>*<srcDispF>*<(urlHistoryF>*<htmlDisplayF)>=^<toHtmlhttpMsgDispF=nameF“MsgDisp”$“进度:”`labLeftOfF`displayFurlefetchf'=post>^=<urlefetchf>=^<stripeany哪里发布消息=。。。urlInputF=…解析URL…stringInputF…showURL。。。srcDispF=。。。urlHistoryF=。。。
|
图86。WWW浏览器
--主要的福吉进来了WWW浏览器。
笨蛋urlFetchF
实现为并行处理不同协议的混混。这是显示在图87.功能发行
提取物请求URL中的协议字段,并将请求发送到适当的子预算。urlFetchF::F HttpRequest HttpResponseurlFetchF=snd>^=<listF fetchers>=^<distr哪里取款机=[(“文件”,fileFetchF>=^<reqURL),--本地文件和ftp(“http”,httpFetchF),--http和gopher请求(“新闻”,newsFetchF>=^<reqURL),(“telnet”,telnetStarterF>=^<reqURL)]distr req@(HttpReq{reqURL=url})=(fetcher,req)哪里取数器=。。。
|
图87.福吉urlFetchF
.
单个协议的实现fudget内核是以延续的风格写的。对于http协议,以下内容执行的操作:
- 在高级输入中接收请求。
- 提取URL的主机字段,并建立一个套接字连接对该主机已打开。如果使用代理,则连接到而是打开代理。
- 请求被发送到主机(或代理)。
- 将收到成批的答复(请参见第14.1条)以及组装好,连接关闭。
- 如果收到重定向响应,则进程将从重新启动步骤2和重定向URL。
- 如果接收到正常或错误响应,则将其放入在高级输出流中。
NNTP(news)协议的实现类似。A区别在于NNTP协议可以处理多个请求每个连接,因此请求后连接保持打开状态已完成,以便在下一个请求为指向同一主机。通常是这样的,因为你通常从同一本地新闻中获取所有新闻文章服务器。(有可能,但不常见,具体说明在URL中显式显示新闻服务器。)FTP协议还可以处理每个连接,因为您需要先登录才能对文件的重用,甚至对传输文件更有利。
FTP协议的不同之处在于它使用控制连接用于发送启动文件传输和单独的数据连接每次文件传输。这个数据连接通常由服务器启动,连接到套接字由客户指定。在胡说八道实现,这两个连接由两个独立的,但是合作,福吉。
HTML文档包含一系列元素,这些元素是由标记分隔。元素可以包含纯文本和其他,嵌套元素。例如,
<H1>fudget<TT>htmlDisplayF</TT></H1>
是标记为顶层标题的元素,并且它具有嵌套标记为用打字机字体显示的元素。块级元素和文本级元素。以前的标记文本块被视为完整的段落。它们是这样组成的垂直地。标题元素是块级别的示例元素。后者标记任意字符序列在一个段落内。块级元素可以包含文本级元素(如上例所示),但反之亦然。
wwbrowser利用块级和文本级元素。这使得布局更容易。功能解析HTML
在顶层是块级元素的序列。纯文本发生在顶层,任何块级元素之外,被理解为发生在隐含段落中(<P>
)元素。比如说,
<H1>fudget<TT>htmlDisplayF</TT></H1>实施。。。
与分析到相同的语法树中
<H1>fudget<TT>htmlDisplayF</TT></H1><P> 实施…</P>
通过这种方法,函数图纸HTMLDOC
可以简单地递归到语法树下,组成使用垂直
和文本级元素使用段落
.网页不仅包含文本,还包含图像和表单元素。在WWWBrowser中,这些是通过嵌入画画里有福吉。我们介绍这个类型活动绘图
对于包含激活元件和定义类型HTML绘图
以上介绍为
类型HtmlDrawing=ActiveDrawing HtmlLabel Gfx HtmlInput HtmlOutput类型ActiveDrawing lbl leaf i o=绘图lbl(任一(F i o)叶)
哪里HTML输入
和HTML输出
是消息类型吗被福吉特人用来实现图像和表单。元素特殊功能用类型的标签标记HTML标签
。当前,超链接、链接目标、窗体和图像贴图已标记。要显示的活动绘图
s、 概括图形F
(参见第27.5.1条)已定义:
动态图形SF::F(任意一个(GfxCommand(ActiveDrawing lbl leaf io))(Int,i))(GfxEvent(Int,o)之一)
笨蛋HTML显示
使用活动图形SF
显示HTML文档。它还包含- 一个流处理器,它收集并生成适当的
Http请求
当表单的提交按钮被按下时。 - 福吉的一个例子
图像获取F
(如下所述)图像模糊与之通信以获得图像它们应该显示出来。
HTML文档中的图像是通过使用URL的引用包含的分别从它们的来源获取。笨蛋HTML显示
用软糖图像获取F
为此:
imageFetchF::F ImageReq(ImageReq,ImageResp)类型ImageReq=(URL,可能是大小)类型ImageResp=(大小,PixmapId)
由处理的请求图像获取F
包含的URL要获取的图像和可选的所需大小图像应缩放。响应包含实际大小(缩放后)和pixmap标识符。因为文档可能包含许多图像以及所需的时间获取图像通常由网络延迟控制与带宽限制相比,获取几个图像并行。笨蛋解析器服务器
,
parServerF::Int->F req resp->F req resp
是一个用于创建可以处理多个并行请求。如果服务器F是个能应付的笨蛋按顺序请求请求和回应,然后是胡说八道解析器服务器 n 服务器F
处理高达n请求平行的。客户解析器服务器
一定有办法告诉哪个响应属于哪个请求,因为响应的传递顺序是不保证对应于接收请求的顺序。笨蛋图像获取F
通过包含请求来实现这一点在回答中。
我们还希望避免获取同一图像两次。这是通过使用缓存来解决问题,
请求(请求)->缓存请求F(客户,请求)(客户,(请求,响应))
除了缓存响应,它还跟踪多个客户端,避免将同一请求发送两次到服务器即使两个客户机在同一时间发送相同的请求时间。(这种情况很容易在HTML显示
,因为同一个图像经常出现在几个地方在同一个HTML文档中。)在WWWBrowser中,这样的组合用于获取图像:
cacheF(parServerF 5 imageFetchF)
实施解析器服务器
如所示图88实施缓存
显示在里面图89.parServerF::Int->F req resp->F req respparServerF n服务器=环通hrightf(absF ctrlSP0)服务器sf哪里serversF=listF[(i,serverF)| i<-ns]--n个并行服务器ns=[1..n]--服务器编号ctrlSP0=ctrlSP ns--ctrlSP的参数是当前可用服务器的列表ctrlSP服务器=案例服务器属于
--如果所有服务器都忙,请等待响应。[]->getLeftSP$fromServer--如果有免费服务器:s: 服务器->getSP$fromServer fromClient哪里fromClient请求=--收到请求后,将其发送到
--空闲列表中的第一个服务器并继续
--因为还押服务器还在免费名单上。putSP(左(s,req))$ctrlSP服务器哪里从服务器(n,resp)=--当从服务器接收到响应时
--输出它并将服务器添加到空闲列表中。putSP(右响应)$ctrlSP(n:服务器) |
图88.福吉解析器服务器
.
cacheF::Eq req=>F请求(req,resp)->F(客户机,请求)(客户机,(req,resp))cacheF serverF=loopthroughthrightf(absF(cacheSP[]])serverF缓存SP缓存挂起=getSP$answerFromServerSP请求fromclientsp哪里从客户端请求SP(n,req)=--来自客户n的请求。assoc oldSP newSP缓存请求哪里旧SP AN=--答案在缓存中找到了。putSP(右(n,(req,ans)))$缓存SP缓存挂起新闻=--一个新请求,发送到服务器,然后
--将客户端添加到挂起列表。
如果req`elem`map snd挂起然后续其他的putSP(左请求)cont哪里cont=缓存SP缓存((n,req):挂起)answerFromServerSP ans@(请求,)=--服务器发送了对请求请求的应答,
--把它保存在缓存里,
--将其转发给等待的客户端并将其从
--待定列表。PUTSP[右(n,ans)|(n,uu)<-准备就绪]$cacheSP(ans:cache)挂起'哪里(=,准备就绪(=待处理部分)。snd)待定 |
图89.福吉特缓存。
与其他现代浏览器相比,wwbrowser有一个缺点浏览器,它不会以增量方式显示文档它们是从网络接收的。这是由于以下几个事实:- 当前的设计
urlFetchF
不输出任何内容直到收到完整的文件。 - 即使
urlFetchF
被更改为输出块如果收到,则必须连接所有片段在应用函数之前解析HTML
去吧。 - 笨蛋
HTML显示
使用实现图形F
。什么时候图形F
接收新图形到显示时,它计算其大小并调整窗口大小在它在窗口中绘制任何内容之前。这意味着它需要完整的图纸才能显示什么都可以。
实现接收文档增量显示的一种方法会是让urlFetchF
输出包含文档作为懒惰的一开始就列出字符从服务器接收。这将允许您应用解析器和绘图函数立即发送结果绘图到图形F
。解析器解析HTML
和绘图功能图纸HTMLDOC
必须是精心设计,可以懒得产生一些输出即使只有输入的初始片段可用。你也必须改变图形F
所以它不会从计算图形的大小开始。应该是这样延迟计算绘图命令(请参见第27.2条)发送到窗户系统。窗户的大小应该调整一下根据绘图命令生成的位置远已画好。上述解决方案似乎需要引入一些机制的不确定性选择,因为当绘画正在计算和输出文档的命令,程序应该继续对其他输入做出反应。
然而,函数式语言的I/O系统的趋势是使I/O操作更加明确。即使是Fudget库已经放弃了流的表示,因为它是懒惰的支持更简单的确定性实现的列表。因此,在相反的方向。然而,程序的不确定性系统处于高抽象级别,流作为惰性列表似乎有用。
当然,人们可以想出实现增量的方法显示而不以延迟列表的形式进行输入。
- 我们可以让
urlFetchF
将字符块输出为它们变得可用。 - 我们可以创建一个解析库,在流处理器的形式。函数的类型
解析HTML
会是SP字符串Html
而不是字符串->Html
。但是,我们不会实现增量如果继续输出完整的语法树,则显示在一条消息中记录。我们将不得不输出一个文档片段的序列。 - 胡说八道的输入
HTML显示
会是文件片段而不是完整的文档。因此,我们向前迈进了一步HTML编辑器的方向,而不是简单的显示。
但这似乎不是一个好的软件工程创建一个不同的解析库只是因为我们想使用在交互式程序中构造的解析器,但是从Haskell I/O背后的哲学来看,这似乎是我们要做的。我们由此得出的结论是当前的I/O系统在哈斯凯尔,懒惰并不能很好地结合在一起。