1. 首页
  2. 未分类

用Scala实现简单的Web和API服务器

“u003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fpgc-imageu002FRbiRsmjFrZY4rp” img_width=”640″ img_height=”100″ alt=”用Scala实现简单的Web和API服务器” inline=”0″u003Eu003Cpu003E【CSDN 编者按】大家都知道Web和API服务器在u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003E互联网u003Cu002Fiu003E中的重要性,在计算机网络方面提供了最基本的界面。本文主要介绍了怎样利用Scala实现实时聊天网站和API服务器,通过本篇文章,你定将受益匪浅。u003Cu002Fpu003Eu003Cimg src=”http:u002Fu002Fp3.pstatp.comu002Flargeu002Fpgc-imageu002FReBWsZ0AmEBG2J” img_width=”1022″ img_height=”557″ alt=”用Scala实现简单的Web和API服务器” inline=”0″u003Eu003Cpu003E作者 | Haoyiu003Cu002Fpu003Eu003Cpu003E译者 | 弯月,责编 | 刘静u003Cu002Fpu003Eu003Cpu003E出品 | CSDN(ID:CSDNnews)u003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003E以下为译文:u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003EWeb和API服务器是u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003E互联网u003Cu002Fiu003E系统的骨干,它们为计算机通过网络交互提供了基本的界面,特别是在不同公司和组织之间。这篇指南将向你介绍如何利用Scala简单的HTTP服务器,来提供Web内容和API。本文还会介绍一个完整的例子,告诉你如何构建简单的实时聊天网站,同时支持HTML网页和JSON API端点。u003Cu002Fpu003Eu003Cpu003E这篇文及章的目的是介绍怎样用Scala实现简单的HTTP服务器,从而提供网页服务,以响应API请求。我们会建立一个简单的聊天网站,可以让用户发表聊天信息,其他访问网站的用户都可以看见这些信息。为简单起见,我们将忽略认证、性能、用户挂历、数据库持久存储等问题。但是,这篇文章应该足够你开始用Scala构建网站和API服务器了,并为你学习并构建更多产品级项目打下基础。u003Cu002Fpu003Eu003Cpu003E我们将使用Cask web框架:u003Cu002Fpu003Eu003Cpu003Ehttp:u002Fu002Fu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003Ewwwu003Cu002Fiu003E.lihaoyiu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003E.comu003Cu002Fiu003Eu002Fcasku002Fu003Cu002Fpu003Eu003Cpu003ECask是一个Scala的HTTP为框架,可以用来架设简单的网站并迅速运行。u003Cu002Fpu003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fpgc-imageu002FRT4Gwk56bt5iOJ” img_width=”340″ img_height=”57″ alt=”用Scala实现简单的Web和API服务器” inline=”0″u003Eu003Cpu003Eu003Cstrongu003E开始u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E要开始使用Cask,只需下载并解压示例程序:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003E$ curl -L https:u002Fu002Fgithubu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003E.comu003Cu002Fiu003Eu002Flihaoyiu002Fcasku002Freleasesu002Fdownloadu002F0.u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E3.0u003Cu002Fiu003Eu002FminimalApplication-0.u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E3.0u003Cu002Fiu003E.zip > cask.zipu003Cu002Fpu003Eu003Cpu003E$ unzip cask.zipu003Cu002Fpu003Eu003Cpu003E$ cd minimalApplication-0.u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E3.0u003Cu002Fiu003Eu003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E运行find来看看有哪些文件:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003E$ find . -type fu003Cu002Fpu003Eu003Cpu003E.u002Fbuild.scu003Cu002Fpu003Eu003Cpu003E.u002Fappu002Ftestu002Fsrcu002FExampleTests.scalau003Cu002Fpu003Eu003Cpu003E.u002Fappu002Fsrcu002FMinimalApplication.scalau003Cu002Fpu003Eu003Cpu003E.u002Fmillu003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E我们感兴趣的大部分代码都位于appu002Fsrcu002FMinimalApplication.scala中。u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Epackage appu003Cu002Fpu003Eu003Cpu003Eobject MinimalApplication extends cask.MainRoutes{u003Cu002Fpu003Eu003Cpu003E@cask.get(“u002F”)u003Cu002Fpu003Eu003Cpu003Edef u003Cstrong toutiao-origin=”span” class=”highlight-text”u003Ehellou003Cu002Fstrongu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003Eu003Cu002Fstrongu003E= {u003Cu002Fpu003Eu003Cpu003E”Hello World!”u003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cpu003E@cask.post(“u002Fdo-thing”)u003Cu002Fpu003Eu003Cpu003Edef u003Cstrong toutiao-origin=”span” class=”highlight-text”u003EdoThingu003Cu002Fstrongu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003E(request: cask.Request)u003Cu002Fstrongu003E= {u003Cu002Fpu003Eu003Cpu003Enew String(request.readAllBytes).reverseu003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cpu003Einitializeu003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E用build.sc进行构建:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Eimport mill._, scalalib._u003Cu002Fpu003Eu003Cpu003Eobject app extends ScalaModule{u003Cu002Fpu003Eu003Cpu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003Edef scalaVersion = “2.1u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E3.0u003Cu002Fiu003E”u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003Edef ivyDeps = Agg(u003Cu002Fpu003Eu003Cpu003Eivy”com.lihaoyi::cask:0.u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E3.0u003Cu002Fiu003E”u003Cu002Fpu003Eu003Cpu003E)u003Cu002Fpu003Eu003Cpu003Eobject test extends Tests{u003Cu002Fpu003Eu003Cpu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003Edef testFrameworks = Seq(“utest.runner.Framework”)u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003Edef ivyDeps = Agg(u003Cu002Fpu003Eu003Cpu003Eivy”com.lihaoyi::utest::0.7.1″,u003Cu002Fpu003Eu003Cpu003Eivy”com.lihaoyi::requests::0.2.0″,u003Cu002Fpu003Eu003Cpu003E)u003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E如果你使用Intellij,那么可以运行如下命令来设置Intellij项目配置:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003E$ .u002Fmill mill.scalalib.GenIdeau002Fideau003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E现在你可以在Intellij中打开minimalApplication-0.u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E3.0u003Cu002Fiu003Eu002F目录,查看项目的目录,也可以进行编辑。u003Cu002Fpu003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fpgc-imageu002FReBWsZUDU6dASd” img_width=”980″ img_height=”594″ alt=”用Scala实现简单的Web和API服务器” inline=”0″u003Eu003Cpu003E可以利用Mill构建工具运行该程序,只需执行.u002Fmill:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003E$ .u002Fmill -w app.runBackgroundu003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E该命令将在后台运行Cask Web服务器,同时监视文件系统,如果文件发生了变化,则重启服务器。然后我们可以使用浏览器浏览服务器,默认网址是localhost:8080:u003Cu002Fpu003Eu003Cimg src=”http:u002Fu002Fp3.pstatp.comu002Flargeu002Fpgc-imageu002FReBWsZoCbhRiP8″ img_width=”1060″ img_height=”643″ alt=”用Scala实现简单的Web和API服务器” inline=”0″u003Eu003Cpu003E在u002Fdo-thing上还有个POST端点,可以在另一个终端上使用curl来访问:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003E$ curl -X POST –data hello http:u002Fu002Flocalhost:8080u002Fdo-thingollehu003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E可见,它接受数据hello,然后将反转的字符串返回给客户端。u003Cu002Fpu003Eu003Cpu003E然后可以运行appu002Ftestu002Fsrcu002FExampleTests.scala中的自动化测试:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003E$ .u002Fmill clean app.runBackground # stop the webserver running in the backgroundu003Cu002Fpu003Eu003Cpu003E$ .u002Fmill app.testu003Cu002Fpu003Eu003Cpu003E[50u002F56] app.testu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003E.comu003Cu002Fiu003Epileu003Cu002Fpu003Eu003Cpu003E[info] Compiling 1 Scala source to u002FUsersu002Flihaoyiu002Ftestu002FminimalApplication-0.u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E3.0u003Cu002Fiu003Eu002Foutu002Fappu002Ftestu002Fcompileu002Fdestu002Fclasses …u003Cu002Fpu003Eu003Cpu003E[info] Done compiling.u003Cu002Fpu003Eu003Cpu003E[56u002F56] app.test.testu003Cu002Fpu003Eu003Cpu003E——————————– Running Tests ——————————–u003Cu002Fpu003Eu003Cpu003E+ app.ExampleTests.MinimalApplication 629msu003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E现在基本的东西已经运行起来了,我们来重新运行Web服务器:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003E$ .u002Fmill -w app.runBackgroundu003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E然后开始实现我们的聊天网站!u003Cu002Fpu003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fpgc-imageu002FRT7S2kzFTfre26″ img_width=”340″ img_height=”57″ alt=”用Scala实现简单的Web和API服务器” inline=”0″u003Eu003Cpu003Eu003Cstrongu003E提供HTML服务u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E第一件事就是将纯文本的”Hello, World!”转换成HTML网页。最简单的方式就是利用Scalatags这个HTML生成库。要在项目中使用Scalatags,只需将其作为依赖项加入到build.sc文件即可:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Edef ivyDeps = Agg(u003Cu002Fpu003Eu003Cpu003E+ ivy”com.lihaoyi::scalatags:0.7.0″, u003Cu002Fpu003Eu003Cpu003Eivy”com.lihaoyi::cask:0.u003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-6″u003E3.0u003Cu002Fiu003E”u003Cu002Fpu003Eu003Cpu003E)u003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E如果使用Intellij,那么还需要重新运行.u002Fmill mill.scalalib.GenIdeau002Fidea命令,来发现依赖项的变动,然后重新运行.u002Fmill -w app.runBackground让Web服务器重新监听改动。u003Cu002Fpu003Eu003Cpu003E然后,我们可以在MinimalApplication.scala中导入Scalatags:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Epackage u003Cstrong toutiao-origin=”span” class=”highlight-text”u003Eappu003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E+import scalatags.Text.all._u003Cu002Fpu003Eu003Cpu003Eobject MinimalApplication extends cask.MainRoutes{u003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E然后用一段最简单的Scalatags HTML模板替换”Hello, World!”。u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Edef hello = {u003Cu002Fpu003Eu003Cpu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003E- “Hello World!”u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E+ html(u003Cu002Fpu003Eu003Cpu003E+ head,u003Cu002Fpu003Eu003Cpu003E+ body(u003Cu002Fpu003Eu003Cpu003E+ h1(“Hello!”),u003Cu002Fpu003Eu003Cpu003E+ p(“World”)u003Cu002Fpu003Eu003Cpu003E+ )u003Cu002Fpu003Eu003Cpu003E+ ).renderu003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E我们应该可以看到.u002Fmill -w app.runBackground命令重新编译了代码并重启了服务器。然后刷新网页额,就会看到纯文本已经被替换成HTML页面了。u003Cu002Fpu003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fpgc-imageu002FReBWswaEbZcC6O” img_width=”974″ img_height=”593″ alt=”用Scala实现简单的Web和API服务器” inline=”0″u003Eu003Cimg src=”http:u002Fu002Fp3.pstatp.comu002Flargeu002Fpgc-imageu002FRTJXJ1kBqzfCnu” img_width=”340″ img_height=”57″ alt=”用Scala实现简单的Web和API服务器” inline=”0″u003Eu003Cpu003Eu003Cstrongu003EBootstrapu003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E为了让页面更好看一些,我们使用Bootstrap这个CSS框架。只需按照它的指南,使用link标签引入bootstrap:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Ehead(u003Cu002Fpu003Eu003Cpu003E+ link(u003Cu002Fpu003Eu003Cpu003E+ rel := “stylesheet”, u003Cu002Fpu003Eu003Cpu003E+ href := “https:u002Fu002Fstackpath.bootstrapcdnu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003E.comu003Cu002Fiu003Eu002Fbootstrapu002F4.3.1u002Fcssu002Fbootstrap.min.css”u003Cu002Fpu003Eu003Cpu003E+ )u003Cu002Fpu003Eu003Cpu003E),u003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpreu003Eu003Cpu003Ebody(u003Cu002Fpu003Eu003Cpu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003E- h1(“Hello!”),u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003E- p(“World”)u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E+ div(cls := “container”)(u003Cu002Fpu003Eu003Cpu003E+ h1(“Hello!”),u003Cu002Fpu003Eu003Cpu003E+ p(“World”)u003Cu002Fpu003Eu003Cpu003E+ )u003Cu002Fpu003Eu003Cpu003E)u003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E现在字体不太一样了:u003Cu002Fpu003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fpgc-imageu002FReBWswwCbVBt2F” img_width=”975″ img_height=”591″ alt=”用Scala实现简单的Web和API服务器” inline=”0″u003Eu003Cpu003E虽然还不是最漂亮的网站,但现在已经足够了。u003Cu002Fpu003Eu003Cpu003E在本节的末尾,我们修改一下Scalatags的HTML模板,加上硬编码的聊天文本和假的输入框,让它看起来更像一个聊天应用程序。u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Ebody(u003Cu002Fpu003Eu003Cpu003Ediv(cls := “container”)(u003Cu002Fpu003Eu003Cpu003E- h1(“Hello!”),u003Cu002Fpu003Eu003Cpu003E- p(“World”)u003Cu002Fpu003Eu003Cpu003E+ h1(“Scala Chat!”),u003Cu002Fpu003Eu003Cpu003E+ hr,u003Cu002Fpu003Eu003Cpu003E+ div(u003Cu002Fpu003Eu003Cpu003E+ p(b(“alice”), ” “, “Hello World!”),u003Cu002Fpu003Eu003Cpu003E+ p(b(“bob”), ” “, “I am cow, hear me moo”),u003Cu002Fpu003Eu003Cpu003E+ p(b(“charlie”), ” “, “I weigh twice as much as you”)u003Cu002Fpu003Eu003Cpu003E+ ),u003Cu002Fpu003Eu003Cpu003E+ hr,u003Cu002Fpu003Eu003Cpu003E+ div(u003Cu002Fpu003Eu003Cpu003E+ input(`type` := “text”, placeholder := “User name”, width := “20%”),u003Cu002Fpu003Eu003Cpu003E+ input(`type` := “text”, placeholder := “Please write a message!”, width := “80%”)u003Cu002Fpu003Eu003Cpu003E+ )u003Cu002Fpu003Eu003Cpu003E)u003Cu002Fpu003Eu003Cpu003E)u003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fpgc-imageu002FReBWsxD2JKmOfX” img_width=”1061″ img_height=”648″ alt=”用Scala实现简单的Web和API服务器” inline=”0″u003Eu003Cpu003E现在我们有了一个简单的静态网站,其利用Cask web框架和Scalatags HTML库提供HTML网页服务。现在的服务器代码如下所示:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Epackage appu003Cu002Fpu003Eu003Cpu003Eimport scalatags.Text.all._u003Cu002Fpu003Eu003Cpu003Eobject MinimalApplication extends cask.MainRoutes{u003Cu002Fpu003Eu003Cpu003E@cask.get(“u002F”)u003Cu002Fpu003Eu003Cpu003Edef hello = {u003Cu002Fpu003Eu003Cpu003Ehtml(u003Cu002Fpu003Eu003Cpu003Ehead(u003Cu002Fpu003Eu003Cpu003Elink(u003Cu002Fpu003Eu003Cpu003Erel := “stylesheet”,u003Cu002Fpu003Eu003Cpu003Ehref := “https:u002Fu002Fstackpath.bootstrapcdnu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003E.comu003Cu002Fiu003Eu002Fbootstrapu002F4.3.1u002Fcssu002Fbootstrap.min.css”u003Cu002Fpu003Eu003Cpu003E)u003Cu002Fpu003Eu003Cpu003E),u003Cu002Fpu003Eu003Cpu003Ebody(u003Cu002Fpu003Eu003Cpu003Ediv(cls := “container”)(u003Cu002Fpu003Eu003Cpu003Eh1(“Scala Chat!”),u003Cu002Fpu003Eu003Cpu003Ehr,u003Cu002Fpu003Eu003Cpu003Ediv(u003Cu002Fpu003Eu003Cpu003Ep(b(“alice”), ” “, “Hello World!”),u003Cu002Fpu003Eu003Cpu003Ep(b(“bob”), ” “, “I am cow, hear me moo”),u003Cu002Fpu003Eu003Cpu003Ep(b(“charlie”), ” “, “I weigh twice as much as you”)u003Cu002Fpu003Eu003Cpu003E),u003Cu002Fpu003Eu003Cpu003Ehr,u003Cu002Fpu003Eu003Cpu003Ediv(u003Cu002Fpu003Eu003Cpu003Einput(`type` := “text”, placeholder := “User name”, width := “20%”),u003Cu002Fpu003Eu003Cpu003Einput(`type` := “text”, placeholder := “Please write a message!”, width := “80%”)u003Cu002Fpu003Eu003Cpu003E)u003Cu002Fpu003Eu003Cpu003E)u003Cu002Fpu003Eu003Cpu003E)u003Cu002Fpu003Eu003Cpu003E).renderu003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cpu003Einitializeu003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E接下来,我们来看看怎样让它支持交互!u003Cu002Fpu003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fpgc-imageu002FRTJXJ7YR5xGDl” img_width=”340″ img_height=”57″ alt=”用Scala实现简单的Web和API服务器” inline=”0″u003Eu003Cpu003Eu003Cstrongu003E表单和数据u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E为网站添加交互的第一次尝试是使用HTML表单。首先我们要删掉硬编码的消息列表,转而根据数据来输出HTML网页:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Eobject MinimalApplication extends cask.MainRoutes{u003Cu002Fpu003Eu003Cpu003E+ var messages = Vector(u003Cu002Fpu003Eu003Cpu003E+ (“alice”, “Hello World!”),u003Cu002Fpu003Eu003Cpu003E+ (“bob”, “I am cow, hear me moo”),u003Cu002Fpu003Eu003Cpu003E+ (“charlie”, “I weigh twice as much as you”),u003Cu002Fpu003Eu003Cpu003E+ )u003Cu002Fpu003Eu003Cpu003E@cask.get(“u002F”)u003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpreu003Eu003Cpu003Ediv(u003Cu002Fpu003Eu003Cpu003E- p(b(“alice”), ” “, “Hello World!”),u003Cu002Fpu003Eu003Cpu003E- p(b(“bob”), ” “, “I am cow, hear me moo”),u003Cu002Fpu003Eu003Cpu003E- p(b(“charlie”), ” “, “I weight twice as much as you”)u003Cu002Fpu003Eu003Cpu003E+ for((name, msg) <- messages)u003Cu002Fpu003Eu003Cpu003E+ yield p(b(name), ” “, msg)u003Cu002Fpu003Eu003Cpu003E),u003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E这里我们简单地使用了内存上的mssages存储。关于如何将消息持久存储到数据库中,我将在以后的文章中介绍。u003Cu002Fpu003Eu003Cpu003E接下来,我们需要让页面底部的两个input支持交互。为实现这一点,我们需要将它们包裹在form元素中:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Ehr,u003Cu002Fpu003Eu003Cpu003E- div(u003Cu002Fpu003Eu003Cpu003E- input(`type` := “text”, placeholder := “User name”, width := “20%”),u003Cu002Fpu003Eu003Cpu003E- input(`type` := “text”, placeholder := “Please write a message!”, width := “80%”)u003Cu002Fpu003Eu003Cpu003E+ form(action := “u002F”, method := “post”)(u003Cu002Fpu003Eu003Cpu003E+ input(`type` := “text”, name := “name”, placeholder := “User name”, width := “20%”),u003Cu002Fpu003Eu003Cpu003E+ input(`type` := “text”, name := “msg”, placeholder := “Please write a message!”, width := “60%”),u003Cu002Fpu003Eu003Cpu003E+ input(`type` := “submit”, width := “20%”)u003Cu002Fpu003Eu003Cpu003E)u003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E这样我们就有了一个可以交互的表单,外观跟之前的差不多。但是,提交表单会导致Error 404: Not Found错误。这是因为我们还没有将表单与服务器连接起来,来处理表单提交并获取新的聊天信息。我们可以这样做:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003E- )u003Cu002Fpu003Eu003Cpu003E+u003Cu002Fpu003Eu003Cpu003E+ @cask.postForm(“u002F”)u003Cu002Fpu003Eu003Cpu003E+ def u003Cstrong toutiao-origin=”span” class=”highlight-text”u003EpostHellou003Cu002Fstrongu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003E(name: String, msg: String)u003Cu002Fstrongu003E= {u003Cu002Fpu003Eu003Cpu003E+ messages = messages :+ (name -> msg)u003Cu002Fpu003Eu003Cpu003E+ hellou003Cu002Fpu003Eu003Cpu003E+ }u003Cu002Fpu003Eu003Cpu003E+u003Cu002Fpu003Eu003Cpu003E@cask.get(“u002F”)u003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E@cast.postForm定义为根URL(即 u002F )添加了另一个处理函数,但该处理函数处理POST请求,而不处理GET请求。Cask文档(http:u002Fu002Fu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003Ewwwu003Cu002Fiu003E.lihaoyiu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003E.comu003Cu002Fiu003Eu002Fcasku002F)中还有关于@cask.*注释的其他例子,你可以利用它们来定义处理函数。u003Cu002Fpu003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fpgc-imageu002FReBWtLZ4aLOsAI” img_width=”975″ img_height=”599″ alt=”用Scala实现简单的Web和API服务器” inline=”0″u003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fpgc-imageu002FReBWtM3eij5k2″ img_width=”978″ img_height=”598″ alt=”用Scala实现简单的Web和API服务器” inline=”0″u003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fpgc-imageu002FRTJXJ7uHXU5GZc” img_width=”340″ img_height=”57″ alt=”用Scala实现简单的Web和API服务器” inline=”0″u003Eu003Cpu003Eu003Cstrongu003E验证u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E现在,用户能够以任何名字提交任何评论。但是,并非所有的评论和名字都是有效的:最低限度,我们希望保证评论和名字字段非空,同时我们还需要限制最大长度。u003Cu002Fpu003Eu003Cpu003E实现这一点很简单:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003E@cask.postForm(“u002F”)u003Cu002Fpu003Eu003Cpu003Edef u003Cstrong toutiao-origin=”span” class=”highlight-text”u003EpostHellou003Cu002Fstrongu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003E(name: String, msg: String)u003Cu002Fstrongu003E= {u003Cu002Fpu003Eu003Cpu003E- messages = messages :+ (name -> msg)u003Cu002Fpu003Eu003Cpu003E+ if (name != “” && name.length < 10 && msg != “” && msg.length < 160){u003Cu002Fpu003Eu003Cpu003E+ messages = messages :+ (name -> msg)u003Cu002Fpu003Eu003Cpu003E+ }u003Cu002Fpu003Eu003Cpu003Ehellou003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E这样就可以阻止用户输入非法的name和msg,但出现了另一个问题:用户输入了非法的名字或信息并提交,那么这些信息就会消失,而且不会为错误产生任何反馈。解决方法是,给hello页面渲染一个可选的错误信息,用它来告诉用户出现了什么问题:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003E@cask.postForm(“u002F”)u003Cu002Fpu003Eu003Cpu003Edef u003Cstrong toutiao-origin=”span” class=”highlight-text”u003EpostHellou003Cu002Fstrongu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003E(name: String, msg: String)u003Cu002Fstrongu003E= {u003Cu002Fpu003Eu003Cpu003E- if (name != “” && name.length < 10 && msg != “” && msg.length < 160){u003Cu002Fpu003Eu003Cpu003E- messages = messages :+ (name -> msg)u003Cu002Fpu003Eu003Cpu003E- }u003Cu002Fpu003Eu003Cpu003E- hellou003Cu002Fpu003Eu003Cpu003E+ if (name == “”) hello(Some(“Name cannot be empty”))u003Cu002Fpu003Eu003Cpu003E+ else if (name.length >= 10) hello(Some(“Name cannot be longer than 10 characters”))u003Cu002Fpu003Eu003Cpu003E+ else if (msg == “”) hello(Some(“Message cannot be empty”))u003Cu002Fpu003Eu003Cpu003E+ else if (msg.length >= 160) hello(Some(“Message cannot be longer than 160 characters”))u003Cu002Fpu003Eu003Cpu003E+ else {u003Cu002Fpu003Eu003Cpu003E+ messages = messages :+ (name -> msg)u003Cu002Fpu003Eu003Cpu003E+ hellou003Cu002Fpu003Eu003Cpu003E+ }u003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpreu003Eu003Cpu003E@cask.get(“u002F”)u003Cu002Fpu003Eu003Cpu003E- def u003Cstrong toutiao-origin=”span” class=”highlight-text”u003Ehellou003Cu002Fstrongu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003Eu003Cu002Fstrongu003E= {u003Cu002Fpu003Eu003Cpu003E+ def u003Cstrong toutiao-origin=”span” class=”highlight-text”u003Ehellou003Cu002Fstrongu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003E(errorOpt: Option[String] = None)u003Cu002Fstrongu003E= {u003Cu002Fpu003Eu003Cpu003Ehtml(u003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpreu003Eu003Cpu003Ehr,u003Cu002Fpu003Eu003Cpu003E+ for(error <- errorOpt) u003Cu002Fpu003Eu003Cpu003E+ yield i(color.red)(error),u003Cu002Fpu003Eu003Cpu003Eform(action := “u002F”, method := “post”)(u003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E现在,当名字或信息非法时,就可以正确地显示出错误信息了。u003Cu002Fpu003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fpgc-imageu002FReBWtN857fxsfv” img_width=”978″ img_height=”600″ alt=”用Scala实现简单的Web和API服务器” inline=”0″u003Eu003Cpu003E下一次提交时错误信息就会消失。u003Cu002Fpu003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fpgc-imageu002FRTLSNam5ZxLDlM” img_width=”340″ img_height=”57″ alt=”用Scala实现简单的Web和API服务器” inline=”0″u003Eu003Cpu003Eu003Cstrongu003E记住名字和消息u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E现在比较烦人的是,每次向聊天室中输入消息时,都要重新输入用户名。此外,如果用户名或信息非法,那消息就会被清除,只能重新输入并提交。可以让hello页面处理函数来填充这些字段,这样就可以解决:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003E@cask.get(“u002F”)u003Cu002Fpu003Eu003Cpu003E- def hello(errorOpt: Option[String] = None) = {u003Cu002Fpu003Eu003Cpu003E+ def hello(errorOpt: Option[String] = None, u003Cu002Fpu003Eu003Cpu003E+ userName: Option[String] = None,u003Cu002Fpu003Eu003Cpu003E+ msg: Option[String] = None) = {u003Cu002Fpu003Eu003Cpu003Ehtml(u003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpreu003Eu003Cpu003Eform(action := “u002F”, method := “post”)(u003Cu002Fpu003Eu003Cpu003E- input(`type` := “text”, name := “name”, placeholder := “User name”, width := “20%”, userName.map(value := _)),u003Cu002Fpu003Eu003Cpu003E- input(`type` := “text”, name := “msg”, placeholder := “Please write a message!”, width := “60%”),u003Cu002Fpu003Eu003Cpu003E+ input(u003Cu002Fpu003Eu003Cpu003E+ `type` := “text”, u003Cu002Fpu003Eu003Cpu003E+ name := “name”, u003Cu002Fpu003Eu003Cpu003E+ placeholder := “User name”, u003Cu002Fpu003Eu003Cpu003E+ width := “20%”, u003Cu002Fpu003Eu003Cpu003E+ userName.map(value := _)u003Cu002Fpu003Eu003Cpu003E+ ),u003Cu002Fpu003Eu003Cpu003E+ input(u003Cu002Fpu003Eu003Cpu003E+ `type` := “text”,u003Cu002Fpu003Eu003Cpu003E+ name := “msg”,u003Cu002Fpu003Eu003Cpu003E+ placeholder := “Please write a message!”, u003Cu002Fpu003Eu003Cpu003E+ width := “60%”,u003Cu002Fpu003Eu003Cpu003E+ msg.map(value := _)u003Cu002Fpu003Eu003Cpu003E+ ),u003Cu002Fpu003Eu003Cpu003Einput(`type` := “submit”, width := “20%”)u003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E这里我们使用了可选的userName和msg查询参数,如果它们存在,则将其作为HTML input标签的value的默认值。u003Cu002Fpu003Eu003Cpu003E接下来在postHello的处理函数中渲染页面时,填充userName和msg,再发送给用户:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Edef u003Cstrong toutiao-origin=”span” class=”highlight-text”u003EpostHellou003Cu002Fstrongu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003E(name: String, msg: String)u003Cu002Fstrongu003E= {u003Cu002Fpu003Eu003Cpu003E- if (name == “”) hello(Some(“Name cannot be empty”))u003Cu002Fpu003Eu003Cpu003E- else if (name.length >= 10) hello(Some(“Name cannot be longer than 10 characters”))u003Cu002Fpu003Eu003Cpu003E- else if (msg == “”) hello(Some(“Message cannot be empty”))u003Cu002Fpu003Eu003Cpu003E- else if (msg.length >= 160) hello(Some(“Message cannot be longer than 160 characters”))u003Cu002Fpu003Eu003Cpu003E+ if (name == “”) hello(Some(“Name cannot be empty”), Some(name), Some(msg))u003Cu002Fpu003Eu003Cpu003E+ else if (name.length >= 10) hello(Some(“Name cannot be longer than 10 characters”), Some(name), Some(msg))u003Cu002Fpu003Eu003Cpu003E+ else if (msg == “”) hello(Some(“Message cannot be empty”), Some(name), Some(msg))u003Cu002Fpu003Eu003Cpu003E+ else if (msg.length >= 160) hello(Some(“Message cannot be longer than 160 characters”), Some(name), Some(msg))u003Cu002Fpu003Eu003Cpu003Eelse {u003Cu002Fpu003Eu003Cpu003Emessages = messages :+ (name -> msg)u003Cu002Fpu003Eu003Cpu003E- hellou003Cu002Fpu003Eu003Cpu003E+ hello(None, Some(name), None)u003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E注意任何情况下我们都保留name,但只有错误的情况才保留msg。这样做是正确的,因为我们只希望用户在出错时才进行编辑并重新提交。u003Cu002Fpu003Eu003Cpu003E完整的代码MinimalApplication.scala如下所示:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Epackage appu003Cu002Fpu003Eu003Cpu003Eimport scalatags.Text.all._u003Cu002Fpu003Eu003Cpu003Eobject MinimalApplication extends cask.MainRoutes{u003Cu002Fpu003Eu003Cpu003Evar messages = Vector(u003Cu002Fpu003Eu003Cpu003E(“alice”, “Hello World!”),u003Cu002Fpu003Eu003Cpu003E(“bob”, “I am cow, hear me moo”),u003Cu002Fpu003Eu003Cpu003E(“charlie”, “I weigh twice as you”),u003Cu002Fpu003Eu003Cpu003E)u003Cu002Fpu003Eu003Cpu003E@cask.postForm(“u002F”)u003Cu002Fpu003Eu003Cpu003Edef postHello(name: String, msg: String) = {u003Cu002Fpu003Eu003Cpu003Eif (name == “”) hello(Some(“Name cannot be empty”), Some(name), Some(msg))u003Cu002Fpu003Eu003Cpu003Eelse if (name.length >= 10) hello(Some(“Name cannot be longer than 10 characters”), Some(name), Some(msg))u003Cu002Fpu003Eu003Cpu003Eelse if (msg == “”) hello(Some(“Message cannot be empty”), Some(name), Some(msg))u003Cu002Fpu003Eu003Cpu003Eelse if (msg.length >= 160) hello(Some(“Message cannot be longer than 160 characters”), Some(name), Some(msg))u003Cu002Fpu003Eu003Cpu003Eelse {u003Cu002Fpu003Eu003Cpu003Emessages = messages :+ (name -> msg)u003Cu002Fpu003Eu003Cpu003Ehello(None, Some(name), None)u003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cpu003E@cask.get(“u002F”)u003Cu002Fpu003Eu003Cpu003Edef hello(errorOpt: Option[String] = None,u003Cu002Fpu003Eu003Cpu003EuserName: Option[String] = None,u003Cu002Fpu003Eu003Cpu003Emsg: Option[String] = None) = {u003Cu002Fpu003Eu003Cpu003Ehtml(u003Cu002Fpu003Eu003Cpu003Ehead(u003Cu002Fpu003Eu003Cpu003Elink(u003Cu002Fpu003Eu003Cpu003Erel := “stylesheet”,u003Cu002Fpu003Eu003Cpu003Ehref := “https:u002Fu002Fstackpath.bootstrapcdnu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003E.comu003Cu002Fiu003Eu002Fbootstrapu002F4.3.1u002Fcssu002Fbootstrap.min.css”u003Cu002Fpu003Eu003Cpu003E)u003Cu002Fpu003Eu003Cpu003E),u003Cu002Fpu003Eu003Cpu003Ebody(u003Cu002Fpu003Eu003Cpu003Ediv(cls := “container”)(u003Cu002Fpu003Eu003Cpu003Eh1(“Scala Chat!”),u003Cu002Fpu003Eu003Cpu003Ehr,u003Cu002Fpu003Eu003Cpu003Ediv(u003Cu002Fpu003Eu003Cpu003Efor((name, msg) <- messages)u003Cu002Fpu003Eu003Cpu003Eyield p(b(name), ” “, msg)u003Cu002Fpu003Eu003Cpu003E),u003Cu002Fpu003Eu003Cpu003Ehr,u003Cu002Fpu003Eu003Cpu003Efor(error <- errorOpt)u003Cu002Fpu003Eu003Cpu003Eyield i(color.red)(error),u003Cu002Fpu003Eu003Cpu003Eform(action := “u002F”, method := “post”)(u003Cu002Fpu003Eu003Cpu003Einput(u003Cu002Fpu003Eu003Cpu003E`type` := “text”,u003Cu002Fpu003Eu003Cpu003Ename := “name”,u003Cu002Fpu003Eu003Cpu003Eplaceholder := “User name”,u003Cu002Fpu003Eu003Cpu003Ewidth := “20%”,u003Cu002Fpu003Eu003Cpu003EuserName.map(value := _)u003Cu002Fpu003Eu003Cpu003E),u003Cu002Fpu003Eu003Cpu003Einput(u003Cu002Fpu003Eu003Cpu003E`type` := “text”,u003Cu002Fpu003Eu003Cpu003Ename := “msg”,u003Cu002Fpu003Eu003Cpu003Eplaceholder := “Please write a message!”,u003Cu002Fpu003Eu003Cpu003Ewidth := “60%”,u003Cu002Fpu003Eu003Cpu003Emsg.map(value := _)u003Cu002Fpu003Eu003Cpu003E),u003Cu002Fpu003Eu003Cpu003Einput(`type` := “submit”, width := “20%”)u003Cu002Fpu003Eu003Cpu003E)u003Cu002Fpu003Eu003Cpu003E)u003Cu002Fpu003Eu003Cpu003E)u003Cu002Fpu003Eu003Cpu003E).renderu003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cpu003Einitializeu003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cimg src=”http:u002Fu002Fp3.pstatp.comu002Flargeu002Fpgc-imageu002FRTRIuPNI7JPB03″ img_width=”340″ img_height=”57″ alt=”用Scala实现简单的Web和API服务器” inline=”0″u003Eu003Cpu003Eu003Cstrongu003E利用Ajax实现动态页面更新u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E现在有了一个简单的、基于表单的聊天网站,用户可以发表消息,其他用户加载页面即可看到已发表的消息。下一步就是让网站变成动态的,这样用户不需要刷新页面就能发表消息了。u003Cu002Fpu003Eu003Cpu003E为实现这一点,我们需要做两件事情:u003Cu002Fpu003Eu003Cpu003E允许HTTP服务器发送网页的一部分,例如接收消息并渲染消息列表,而不是渲染整个页面u003Cu002Fpu003Eu003Cpu003E添加一些Javascript来手动提交表单数据。u003Cu002Fpu003Eu003Cimg src=”http:u002Fu002Fp3.pstatp.comu002Flargeu002Fpgc-imageu002FRTRIuPaahjHjS” img_width=”340″ img_height=”57″ alt=”用Scala实现简单的Web和API服务器” inline=”0″u003Eu003Cpu003Eu003Cstrongu003E渲染页面的一部分u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E要想只渲染需要更新的那部分页面,我们可以重构下代码,从hello页面处理函数中提取出messageList辅助函数:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003E)u003Cu002Fpu003Eu003Cpu003E+ u003Cu002Fpu003Eu003Cpu003E+ def messageList = {u003Cu002Fpu003Eu003Cpu003E+ frag(u003Cu002Fpu003Eu003Cpu003E+ for((name, msg) <- messages)u003Cu002Fpu003Eu003Cpu003E+ yield p(b(name), ” “, msg)u003Cu002Fpu003Eu003Cpu003E+ )u003Cu002Fpu003Eu003Cpu003E+ }u003Cu002Fpu003Eu003Cpu003E+u003Cu002Fpu003Eu003Cpu003E@cask.postForm(“u002F”)u003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpreu003Eu003Cpu003Ehr,u003Cu002Fpu003Eu003Cpu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003E- div(u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003E- for((name, msg) <- messages)u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003E- yield p(b(name), ” “, msg)u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E+ div(id := “messageList”)(u003Cu002Fpu003Eu003Cpu003E+ messageListu003Cu002Fpu003Eu003Cpu003E),u003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E接下来,我们可以修改postHello处理函数,从而仅渲染可能发生了变化的messageList,而不是渲染整个页面:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003E- @cask.postForm(“u002F”)u003Cu002Fpu003Eu003Cpu003E- def u003Cstrong toutiao-origin=”span” class=”highlight-text”u003EpostHellou003Cu002Fstrongu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003E(name: String, msg: String)u003Cu002Fstrongu003E= {u003Cu002Fpu003Eu003Cpu003E- if (name == “”) hello(Some(“Name cannot be empty”), Some(name), Some(msg))u003Cu002Fpu003Eu003Cpu003E- else if (name.length >= 10) hello(Some(“Name cannot be longer than 10 characters”), Some(name), Some(msg))u003Cu002Fpu003Eu003Cpu003E- else if (msg == “”) hello(Some(“Message cannot be empty”), Some(name), Some(msg))u003Cu002Fpu003Eu003Cpu003E- else if (msg.length >= 160) hello(Some(“Message cannot be longer than 160 characters”), Some(name), Some(msg))u003Cu002Fpu003Eu003Cpu003E- else {u003Cu002Fpu003Eu003Cpu003E- messages = messages :+ (name -> msg)u003Cu002Fpu003Eu003Cpu003E- hello(None, Some(name), None)u003Cu002Fpu003Eu003Cpu003E+ @cask.postJson(“u002F”)u003Cu002Fpu003Eu003Cpu003E+ def u003Cstrong toutiao-origin=”span” class=”highlight-text”u003EpostHellou003Cu002Fstrongu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003E(name: String, msg: String)u003Cu002Fstrongu003E= {u003Cu002Fpu003Eu003Cpu003E+ if (name == “”) ujson.Obj(“success” -> false, “txt” -> “Name cannot be empty”)u003Cu002Fpu003Eu003Cpu003E+ else if (name.length >= 10) ujson.Obj(“success” -> false, “txt” -> “Name cannot be longer than 10 characters”)u003Cu002Fpu003Eu003Cpu003E+ else if (msg == “”) ujson.Obj(“success” -> false, “txt” -> “Message cannot be empty”)u003Cu002Fpu003Eu003Cpu003E+ else if (msg.length >= 160) ujson.Obj(“success” -> false, “txt” -> “Message cannot be longer than 160 characters”)u003Cu002Fpu003Eu003Cpu003E+ else {u003Cu002Fpu003Eu003Cpu003E+ messages = messages :+ (name -> msg)u003Cu002Fpu003Eu003Cpu003E+ ujson.Obj(“success” -> true, “txt” -> messageList.render)u003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E注意我们这里用@cask.postJson替换了@cask.postForm,此外不再调用hello来重新渲染整个页面,而是仅返回一个很小的JSON结构ujson.Obj,这样浏览器可以利用它更新HTML页面。ujson.Obj数据类型由uJson库提供。u003Cu002Fpu003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fpgc-imageu002FRTYlVXmICmMrFE” img_width=”340″ img_height=”57″ alt=”用Scala实现简单的Web和API服务器” inline=”0″u003Eu003Cpu003Eu003Cstrongu003E利用Javascript更新页面u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E现在我们写好了服务器端代码,接下来我们编写相关的客户端代码,从服务器接收JSON响应,并利用它来更新HTML界面u003Cu002Fpu003Eu003Cpu003E要处理客户端逻辑,我们需要给一些关键的HTML元素添加ID,这样才能在Javascript中访问它们:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Ehr,u003Cu002Fpu003Eu003Cpu003E- for(error <- errorOpt)u003Cu002Fpu003Eu003Cpu003E- yield i(color.red)(error),u003Cu002Fpu003Eu003Cpu003E+ div(id := “errorDiv”, color.red),u003Cu002Fpu003Eu003Cpu003Eform(action := “u002F”, method := “post”)(u003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpreu003Eu003Cpu003Einput(u003Cu002Fpu003Eu003Cpu003E`type` := “text”,u003Cu002Fpu003Eu003Cpu003E- name := “name”,u003Cu002Fpu003Eu003Cpu003E+ id := “nameInput”,u003Cu002Fpu003Eu003Cpu003Eplaceholder := “User name”,u003Cu002Fpu003Eu003Cpu003Ewidth := “20%”u003Cu002Fpu003Eu003Cpu003E),u003Cu002Fpu003Eu003Cpu003Einput(u003Cu002Fpu003Eu003Cpu003E`type` := “text”,u003Cu002Fpu003Eu003Cpu003E- name := “msg”,u003Cu002Fpu003Eu003Cpu003E+ id := “msgInput”,u003Cu002Fpu003Eu003Cpu003Eplaceholder := “Please write a message!”,u003Cu002Fpu003Eu003Cpu003Ewidth := “60%”u003Cu002Fpu003Eu003Cpu003E),u003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E接下来,在页面头部引入一系列Javascript:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Ehead(u003Cu002Fpu003Eu003Cpu003Elink(u003Cu002Fpu003Eu003Cpu003Erel := “stylesheet”,u003Cu002Fpu003Eu003Cpu003Ehref := “https:u002Fu002Fstackpath.bootstrapcdnu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003E.comu003Cu002Fiu003Eu002Fbootstrapu002F4.3.1u002Fcssu002Fbootstrap.min.css”u003Cu002Fpu003Eu003Cpu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003E- ),u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E+ )u003Cu002Fpu003Eu003Cpu003E+ script(raw(“””u003Cu002Fpu003Eu003Cpu003E+ function submitForm{u003Cu002Fpu003Eu003Cpu003E+ fetch(u003Cu002Fpu003Eu003Cpu003E+ “u002F”,u003Cu002Fpu003Eu003Cpu003E+ {u003Cu002Fpu003Eu003Cpu003E+ method: “POST”,u003Cu002Fpu003Eu003Cpu003E+ body: JSON.stringify({name: nameInput.value, msg: msgInput.value})u003Cu002Fpu003Eu003Cpu003E+ }u003Cu002Fpu003Eu003Cpu003E+ ).then(response => response.json)u003Cu002Fpu003Eu003Cpu003E+ .then(json => {u003Cu002Fpu003Eu003Cpu003E+ if (json.success) {u003Cu002Fpu003Eu003Cpu003E+ messageList.innerHTML = json.txtu003Cu002Fpu003Eu003Cpu003E+ msgInput.value = “”u003Cu002Fpu003Eu003Cpu003E+ errorDiv.innerText = “”u003Cu002Fpu003Eu003Cpu003E+ } else {u003Cu002Fpu003Eu003Cpu003E+ errorDiv.innerText = json.txtu003Cu002Fpu003Eu003Cpu003E+ }u003Cu002Fpu003Eu003Cpu003E+ })u003Cu002Fpu003Eu003Cpu003E+ }u003Cu002Fpu003Eu003Cpu003E+ “””))u003Cu002Fpu003Eu003Cpu003E),u003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E从表单的onsubmit处理函数中调用该Javascript函数:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003E- form(action := “u002F”, method := “post”)(+ form(onsubmit := “submitForm; return false”)(u003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E这样就可以了。现在向网站添加聊天文本,文本就会立即出现在网页上,之后加载页面的其他人也能看见。u003Cu002Fpu003Eu003Cpu003E最终的代码如下:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Epackage appu003Cu002Fpu003Eu003Cpu003Eimport scalatags.Text.all._u003Cu002Fpu003Eu003Cpu003Eobject MinimalApplication extends cask.MainRoutes{u003Cu002Fpu003Eu003Cpu003Evar messages = Vector(u003Cu002Fpu003Eu003Cpu003E(“alice”, “Hello World!”),u003Cu002Fpu003Eu003Cpu003E(“bob”, “I am cow, hear me moo”),u003Cu002Fpu003Eu003Cpu003E(“charlie”, “I weigh twice as you”),u003Cu002Fpu003Eu003Cpu003E)u003Cu002Fpu003Eu003Cpu003Edef u003Cstrong toutiao-origin=”span” class=”highlight-text”u003EmessageListu003Cu002Fstrongu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003Eu003Cu002Fstrongu003E= {u003Cu002Fpu003Eu003Cpu003Efrag(u003Cu002Fpu003Eu003Cpu003Efor((name, msg) <- messages)u003Cu002Fpu003Eu003Cpu003Eyield p(b(name), ” “, msg)u003Cu002Fpu003Eu003Cpu003E)u003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cpu003E@cask.postJson(“u002F”)u003Cu002Fpu003Eu003Cpu003Edef u003Cstrong toutiao-origin=”span” class=”highlight-text”u003EpostHellou003Cu002Fstrongu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003E(name: String, msg: String)u003Cu002Fstrongu003E= {u003Cu002Fpu003Eu003Cpu003Eif (name == “”) ujson.Obj(“success” -> false, “txt” -> “Name cannot be empty”)u003Cu002Fpu003Eu003Cpu003Eelse if (name.length >= 10) ujson.Obj(“success” -> false, “txt” -> “Name cannot be longer than 10 characters”)u003Cu002Fpu003Eu003Cpu003Eelse if (msg == “”) ujson.Obj(“success” -> false, “txt” -> “Message cannot be empty”)u003Cu002Fpu003Eu003Cpu003Eelse if (msg.length >= 160) ujson.Obj(“success” -> false, “txt” -> “Message cannot be longer than 160 characters”)u003Cu002Fpu003Eu003Cpu003Eelse {u003Cu002Fpu003Eu003Cpu003Emessages = messages :+ (name -> msg)u003Cu002Fpu003Eu003Cpu003Eujson.Obj(“success” -> true, “txt” -> messageList.render)u003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cpu003E@cask.get(“u002F”)u003Cu002Fpu003Eu003Cpu003Edef u003Cstrong toutiao-origin=”span” class=”highlight-text”u003Ehellou003Cu002Fstrongu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003Eu003Cu002Fstrongu003E= {u003Cu002Fpu003Eu003Cpu003Ehtml(u003Cu002Fpu003Eu003Cpu003Ehead(u003Cu002Fpu003Eu003Cpu003Elink(u003Cu002Fpu003Eu003Cpu003Erel := “stylesheet”,u003Cu002Fpu003Eu003Cpu003Ehref := “https:u002Fu002Fstackpath.bootstrapcdnu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003E.comu003Cu002Fiu003Eu002Fbootstrapu002F4.3.1u002Fcssu002Fbootstrap.min.css”u003Cu002Fpu003Eu003Cpu003E),u003Cu002Fpu003Eu003Cpu003Escript(raw(“””u003Cu002Fpu003Eu003Cpu003Efunction submitForm{u003Cu002Fpu003Eu003Cpu003Efetch(u003Cu002Fpu003Eu003Cpu003E”u002F”,u003Cu002Fpu003Eu003Cpu003E{u003Cu002Fpu003Eu003Cpu003Emethod: “POST”,u003Cu002Fpu003Eu003Cpu003Ebody: JSON.stringify({name: nameInput.value, msg: msgInput.value})u003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cpu003E).then(response => response.json)u003Cu002Fpu003Eu003Cpu003E.then(json => {u003Cu002Fpu003Eu003Cpu003Eif (json.success) {u003Cu002Fpu003Eu003Cpu003EmessageList.innerHTML = json.txtu003Cu002Fpu003Eu003Cpu003EmsgInput.value = “”u003Cu002Fpu003Eu003Cpu003EerrorDiv.innerText = “”u003Cu002Fpu003Eu003Cpu003E} else {u003Cu002Fpu003Eu003Cpu003EerrorDiv.innerText = json.txtu003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cpu003E})u003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cpu003E”””))u003Cu002Fpu003Eu003Cpu003E),u003Cu002Fpu003Eu003Cpu003Ebody(u003Cu002Fpu003Eu003Cpu003Ediv(cls := “container”)(u003Cu002Fpu003Eu003Cpu003Eh1(“Scala Chat!”),u003Cu002Fpu003Eu003Cpu003Ehr,u003Cu002Fpu003Eu003Cpu003Ediv(id := “messageList”)(u003Cu002Fpu003Eu003Cpu003EmessageListu003Cu002Fpu003Eu003Cpu003E),u003Cu002Fpu003Eu003Cpu003Ehr,u003Cu002Fpu003Eu003Cpu003Ediv(id := “errorDiv”, color.red),u003Cu002Fpu003Eu003Cpu003Eform(onsubmit := “submitForm; return false”)(u003Cu002Fpu003Eu003Cpu003Einput(u003Cu002Fpu003Eu003Cpu003E`type` := “text”,u003Cu002Fpu003Eu003Cpu003Eid := “nameInput”,u003Cu002Fpu003Eu003Cpu003Eplaceholder := “User name”,u003Cu002Fpu003Eu003Cpu003Ewidth := “20%”u003Cu002Fpu003Eu003Cpu003E),u003Cu002Fpu003Eu003Cpu003Einput(u003Cu002Fpu003Eu003Cpu003E`type` := “text”,u003Cu002Fpu003Eu003Cpu003Eid := “msgInput”,u003Cu002Fpu003Eu003Cpu003Eplaceholder := “Please write a message!”,u003Cu002Fpu003Eu003Cpu003Ewidth := “60%”u003Cu002Fpu003Eu003Cpu003E),u003Cu002Fpu003Eu003Cpu003Einput(`type` := “submit”, width := “20%”)u003Cu002Fpu003Eu003Cpu003E)u003Cu002Fpu003Eu003Cpu003E)u003Cu002Fpu003Eu003Cpu003E)u003Cu002Fpu003Eu003Cpu003E).renderu003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cpu003Einitializeu003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E注意尽管你输入的消息你自己可以立即看到,但其他人只有刷新页面,或者输入自己的消息迫使messageList重新加载,才能看到你的消息。本文的最后一节将介绍怎样让所有人立即看到你的消息,而不需要手动刷新。u003Cu002Fpu003Eu003Cimg src=”http:u002Fu002Fp9.pstatp.comu002Flargeu002Fpgc-imageu002FRTYlVY98d1MEiD” img_width=”340″ img_height=”57″ alt=”用Scala实现简单的Web和API服务器” inline=”0″u003Eu003Cpu003Eu003Cstrongu003E利用Websockets实时更新页面u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E推送更新的概念和简单:每次提交新消息后,就将消息”推送”到所有监听中的浏览器上,而不是等待浏览器刷新并“拉取”更新后的数据。实现这一目的有多种方法。本文我们使用Websockets。u003Cu002Fpu003Eu003Cpu003EWebsockets可以让浏览器和服务器在正常的HTTP请求-响应流之外互相发送消息。连接一旦建立,任何一方都可以在任何时间发送消息,消息可以包含任意字符串或任意字节。u003Cu002Fpu003Eu003Cpu003E我们要实现的流程如下:u003Cu002Fpu003Eu003Colu003Eu003Cliu003Eu003Cpu003E网站加载后,浏览器建立到服务器的websocket连接u003Cu002Fpu003Eu003Cu002Fliu003Eu003Cliu003Eu003Cpu003E连接建立后,浏览器将发送消息”0″到服务器,表明它已准备好接收更新u003Cu002Fpu003Eu003Cu002Fliu003Eu003Cliu003Eu003Cpu003E服务器将响应初始的txt,其中包含所有已经渲染的消息,以及一个index,表示当前的消息计数u003Cu002Fpu003Eu003Cu002Fliu003Eu003Cliu003Eu003Cpu003E每当收到消息时,浏览器就会将最后看到的index发送给服务器,然后等待新消息出现,再按照步骤3进行响应u003Cu002Fpu003Eu003Cu002Fliu003Eu003Cu002Folu003Eu003Cpu003E在服务器上实现这一点的关键就是保持所有已打开的连接的集合:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Evar openConnections = Set.empty[cask.WsChannelActor]u003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E该集合包含当前等待更新的浏览器的列表。每当新消息出现时,我们就会向这个列表进行广播。u003Cu002Fpu003Eu003Cpu003E接下来,定义@cask.websocket处理函数,接收进入的websocket连接并处理:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003E@cask.websocket(“u002Fsubscribe”)u003Cu002Fpu003Eu003Cpu003Edef subscribe = {u003Cu002Fpu003Eu003Cpu003Ecask.WsHandler { connection =>u003Cu002Fpu003Eu003Cpu003Ecask.WsActor {u003Cu002Fpu003Eu003Cpu003Ecase cask.Ws.Text(msg) =>u003Cu002Fpu003Eu003Cpu003Eif (msg.toInt < messages.length){u003Cu002Fpu003Eu003Cpu003Econnection.send(u003Cu002Fpu003Eu003Cpu003Ecask.Ws.Text(u003Cu002Fpu003Eu003Cpu003Eujson.Obj(“index” -> messages.length, “txt” -> messageList.render).renderu003Cu002Fpu003Eu003Cpu003E)u003Cu002Fpu003Eu003Cpu003E)u003Cu002Fpu003Eu003Cpu003E}else{u003Cu002Fpu003Eu003Cpu003EopenConnections += connectionu003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cpu003Ecase cask.Ws.Close(_, _) => openConnections -= connectionu003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E该处理函数接收来自浏览器的msg,检查其内容是否应该立即响应,还是应该利用openConnections注册一个连接再稍后响应。u003Cu002Fpu003Eu003Cpu003E我们需要在postHello处理函数中做类似的修改:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Emessages = messages :+ (name -> msg)u003Cu002Fpu003Eu003Cpu003E+ val notification = cask.Ws.Text(u003Cu002Fpu003Eu003Cpu003E+ ujson.Obj(“index” -> messages.length, “txt” -> messageList.render).renderu003Cu002Fpu003Eu003Cpu003E+ )u003Cu002Fpu003Eu003Cpu003E+ for(conn <- openConnections) conn.send(notification)u003Cu002Fpu003Eu003Cpu003E+ openConnections = Set.emptyu003Cu002Fpu003Eu003Cpu003Eujson.Obj(“success” -> true, “txt” -> messageList.render)u003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E这样,每当新的聊天消息提交时,就会将它发送给所有打开的连接,以通知它们。u003Cu002Fpu003Eu003Cpu003E最后,我们需要在浏览器的script标签中添加一点Javascript代码,来打开Websocket连接,并处理消息的交换:u003Cu002Fpu003Eu003Cpreu003Eu003Cpu003Evar socket = new WebSocket(“ws:u002Fu002F” + location.host + “u002Fsubscribe”);u003Cu002Fpu003Eu003Cpu003Evar eventIndex = 0u003Cu002Fpu003Eu003Cpu003Esocket.onopen = function(ev){ socket.send(“” + eventIndex) }u003Cu002Fpu003Eu003Cpu003Esocket.onmessage = function(ev){u003Cu002Fpu003Eu003Cpu003Evar json = JSON.parse(ev.data)u003Cu002Fpu003Eu003Cpu003EeventIndex = json.indexu003Cu002Fpu003Eu003Cpu003Esocket.send(“” + eventIndex)u003Cu002Fpu003Eu003Cpu003EmessageList.innerHTML = json.txtu003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cpu003E这里,我们首先打开一个连接,发送第一条”0″消息来启动整个流程,然后每次收到更新后,就将json.txt渲染到messageList中,然后将json.index发送回服务器,来订阅下一次更新。u003Cu002Fpu003Eu003Cpu003E现在,同时打开两个浏览器,就会看到一个窗口中发送的聊天消息立即出现在另一个窗口中:u003Cu002Fpu003Eu003Cpu003E本节的完整代码如下:u003Cu002Fpu003Eu003Cimg src=”http:u002Fu002Fp3.pstatp.comu002Flargeu002Fpgc-imageu002FReBWtlb1BjZOFN” img_width=”640″ img_height=”277″ alt=”用Scala实现简单的Web和API服务器” inline=”0″u003Eu003Cpreu003Eu003Cpu003Epackage appu003Cu002Fpu003Eu003Cpu003Eimport scalatags.Text.all._u003Cu002Fpu003Eu003Cpu003Eobject MinimalApplication extends cask.MainRoutes{u003Cu002Fpu003Eu003Cpu003Evar messages = Vector(u003Cu002Fpu003Eu003Cpu003E(“alice”, “Hello World!”),u003Cu002Fpu003Eu003Cpu003E(“bob”, “I am cow, hear me moo”),u003Cu002Fpu003Eu003Cpu003E(“charlie”, “I weigh twice as you”),u003Cu002Fpu003Eu003Cpu003E)u003Cu002Fpu003Eu003Cpu003Evar openConnections = Set.empty[cask.WsChannelActor]u003Cu002Fpu003Eu003Cpu003Edef u003Cstrong toutiao-origin=”span” class=”highlight-text”u003EmessageListu003Cu002Fstrongu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003Eu003Cu002Fstrongu003E= {u003Cu002Fpu003Eu003Cpu003Efrag(u003Cu002Fpu003Eu003Cpu003Efor((name, msg) <- messages)u003Cu002Fpu003Eu003Cpu003Eyield p(b(name), ” “, msg)u003Cu002Fpu003Eu003Cpu003E)u003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cpu003E@cask.postJson(“u002F”)u003Cu002Fpu003Eu003Cpu003Edef u003Cstrong toutiao-origin=”span” class=”highlight-text”u003EpostHellou003Cu002Fstrongu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003E(name: String, msg: String)u003Cu002Fstrongu003E= {u003Cu002Fpu003Eu003Cpu003Eif (name == “”) ujson.Obj(“success” -> false, “txt” -> “Name cannot be empty”)u003Cu002Fpu003Eu003Cpu003Eelse if (name.length >= 10) ujson.Obj(“success” -> false, “txt” -> “Name cannot be longer than 10 characters”)u003Cu002Fpu003Eu003Cpu003Eelse if (msg == “”) ujson.Obj(“success” -> false, “txt” -> “Message cannot be empty”)u003Cu002Fpu003Eu003Cpu003Eelse if (msg.length >= 160) ujson.Obj(“success” -> false, “txt” -> “Message cannot be longer than 160 characters”)u003Cu002Fpu003Eu003Cpu003Eelse {u003Cu002Fpu003Eu003Cpu003Emessages = messages :+ (name -> msg)u003Cu002Fpu003Eu003Cpu003Eval notification = cask.Ws.Text(u003Cu002Fpu003Eu003Cpu003Eujson.Obj(“index” -> messages.length, “txt” -> messageList.render).renderu003Cu002Fpu003Eu003Cpu003E)u003Cu002Fpu003Eu003Cpu003Efor(conn <- openConnections) conn.send(notification)u003Cu002Fpu003Eu003Cpu003EopenConnections = Set.emptyu003Cu002Fpu003Eu003Cpu003Eujson.Obj(“success” -> true, “txt” -> messageList.render)u003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cpu003E@cask.get(“u002F”)u003Cu002Fpu003Eu003Cpu003Edef u003Cstrong toutiao-origin=”span” class=”highlight-text”u003Ehellou003Cu002Fstrongu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003Eu003Cu002Fstrongu003E= {u003Cu002Fpu003Eu003Cpu003Ehtml(u003Cu002Fpu003Eu003Cpu003Ehead(u003Cu002Fpu003Eu003Cpu003Elink(u003Cu002Fpu003Eu003Cpu003Erel := “stylesheet”,u003Cu002Fpu003Eu003Cpu003Ehref := “https:u002Fu002Fstackpath.bootstrapcdnu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003E.comu003Cu002Fiu003Eu002Fbootstrapu002F4.3.1u002Fcssu002Fbootstrap.min.css”u003Cu002Fpu003Eu003Cpu003E),u003Cu002Fpu003Eu003Cpu003Escript(raw(“””u003Cu002Fpu003Eu003Cpu003Efunction submitForm{u003Cu002Fpu003Eu003Cpu003Efetch(u003Cu002Fpu003Eu003Cpu003E”u002F”,u003Cu002Fpu003Eu003Cpu003E{u003Cu002Fpu003Eu003Cpu003Emethod: “POST”,u003Cu002Fpu003Eu003Cpu003Ebody: JSON.stringify({name: nameInput.value, msg: msgInput.value})u003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cpu003E).then(response => response.json)u003Cu002Fpu003Eu003Cpu003E.then(json => {u003Cu002Fpu003Eu003Cpu003Eif (json.success) {u003Cu002Fpu003Eu003Cpu003EmessageList.innerHTML = json.txtu003Cu002Fpu003Eu003Cpu003EmsgInput.value = “”u003Cu002Fpu003Eu003Cpu003EerrorDiv.innerText = “”u003Cu002Fpu003Eu003Cpu003E} else {u003Cu002Fpu003Eu003Cpu003EerrorDiv.innerText = json.txtu003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cpu003E})u003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cpu003Evar socket = new WebSocket(“ws:u002Fu002F” + location.host + “u002Fsubscribe”);u003Cu002Fpu003Eu003Cpu003Esocket.onopen = function(ev){ socket.send(“0”) }u003Cu002Fpu003Eu003Cpu003Esocket.onmessage = function(ev){u003Cu002Fpu003Eu003Cpu003Evar json = JSON.parse(ev.data)u003Cu002Fpu003Eu003Cpu003EmessageList.innerHTML = json.txtu003Cu002Fpu003Eu003Cpu003Esocket.send(“” + json.index)u003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cpu003E”””))u003Cu002Fpu003Eu003Cpu003E),u003Cu002Fpu003Eu003Cpu003Ebody(u003Cu002Fpu003Eu003Cpu003Ediv(cls := “container”)(u003Cu002Fpu003Eu003Cpu003Eh1(“Scala Chat!”),u003Cu002Fpu003Eu003Cpu003Ehr,u003Cu002Fpu003Eu003Cpu003Ediv(id := “messageList”)(u003Cu002Fpu003Eu003Cpu003EmessageListu003Cu002Fpu003Eu003Cpu003E),u003Cu002Fpu003Eu003Cpu003Ehr,u003Cu002Fpu003Eu003Cpu003Ediv(id := “errorDiv”, color.red),u003Cu002Fpu003Eu003Cpu003Eform(onsubmit := “submitForm; return false”)(u003Cu002Fpu003Eu003Cpu003Einput(u003Cu002Fpu003Eu003Cpu003E`type` := “text”,u003Cu002Fpu003Eu003Cpu003Eid := “nameInput”,u003Cu002Fpu003Eu003Cpu003Eplaceholder := “User name”,u003Cu002Fpu003Eu003Cpu003Ewidth := “20%”u003Cu002Fpu003Eu003Cpu003E),u003Cu002Fpu003Eu003Cpu003Einput(u003Cu002Fpu003Eu003Cpu003E`type` := “text”,u003Cu002Fpu003Eu003Cpu003Eid := “msgInput”,u003Cu002Fpu003Eu003Cpu003Eplaceholder := “Please write a message!”,u003Cu002Fpu003Eu003Cpu003Ewidth := “60%”u003Cu002Fpu003Eu003Cpu003E),u003Cu002Fpu003Eu003Cpu003Einput(`type` := “submit”, width := “20%”)u003Cu002Fpu003Eu003Cpu003E)u003Cu002Fpu003Eu003Cpu003E)u003Cu002Fpu003Eu003Cpu003E)u003Cu002Fpu003Eu003Cpu003E).renderu003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cpu003E@cask.websocket(“u002Fsubscribe”)u003Cu002Fpu003Eu003Cpu003Edef u003Cstrong toutiao-origin=”span” class=”highlight-text”u003Esubscribeu003Cu002Fstrongu003Eu003Cstrong toutiao-origin=”span” class=”highlight-text”u003Eu003Cu002Fstrongu003E= {u003Cu002Fpu003Eu003Cpu003Ecask.WsHandler { connection =>u003Cu002Fpu003Eu003Cpu003Ecask.WsActor {u003Cu002Fpu003Eu003Cpu003Ecase cask.Ws.Text(msg) =>u003Cu002Fpu003Eu003Cpu003Eif (msg.toInt < messages.length){u003Cu002Fpu003Eu003Cpu003Econnection.send(u003Cu002Fpu003Eu003Cpu003Ecask.Ws.Text(u003Cu002Fpu003Eu003Cpu003Eujson.Obj(“index” -> messages.length, “txt” -> messageList.render).renderu003Cu002Fpu003Eu003Cpu003E)u003Cu002Fpu003Eu003Cpu003E)u003Cu002Fpu003Eu003Cpu003E}else{u003Cu002Fpu003Eu003Cpu003EopenConnections += connectionu003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cpu003Ecase cask.Ws.Close(_, _) => openConnections -= connectionu003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cpu003Einitializeu003Cu002Fpu003Eu003Cpu003E}u003Cu002Fpu003Eu003Cu002Fpreu003Eu003Cimg src=”http:u002Fu002Fp1.pstatp.comu002Flargeu002Fpgc-imageu002FRTcGEOD4Gd9xON” img_width=”340″ img_height=”57″ alt=”用Scala实现简单的Web和API服务器” inline=”0″u003Eu003Cpu003Eu003Cstrongu003E总结u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E本文我们介绍了怎样利用Scala实现实时聊天网站和API服务器。我们从静态网站开始,添加基于表单的交互,再利用Ajax访问JSON API实现动态页面,最后利用websocket实现推送通知。我们使用了Cask web框架,Scalatags HTML库,以及uJson序列化库,代码大约125行。u003Cu002Fpu003Eu003Cpu003E这里展示的聊天网站非常简单。我们故意忽略了将消息保存到持久数据库、认证、用户账号、多聊天室、使用量限制以及许多其他的功能。这里仅使用了内存上的messages列表和openConnections集合,从并发更新的角度来看,它们并非线程安全的。但无论如何,希望这篇文章能够让你体会到怎样使用Scala实现简单的网站和API服务器,进而用它构建更大、更宏伟的应用程序。u003Cu002Fpu003Eu003Cpu003E英文:Simple Web and Api Servers with Scalau003Cu002Fpu003Eu003Cpu003E原文链接:http:u002Fu002Fu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-4″u003Ewwwu003Cu002Fiu003E.lihaoyiu003Ci class=”chrome-extension-mutihighlight chrome-extension-mutihighlight-style-2″u003E.comu003Cu002Fiu003Eu002Fpostu002FSimpleWebandApiServerswithScala.htmlu003Cu002Fpu003Eu003Cpu003E【END】u003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003ECSDN 博客诚邀入驻啦!u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003E本着共享、协作、开源、技术之路我们共同进步的准则u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cp class=””u003Eu003Cstrongu003E只要你技术够干货,内容够扎实,分享够积极u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cp class=””u003Eu003Cstrongu003E欢迎加入 CSDN 大家庭!u003Cu002Fstrongu003Eu003Cu002Fpu003E”

原文始发于:用Scala实现简单的Web和API服务器

主题测试文章,只做测试使用。发布者:杀手梦三刀,转转请注明出处:http://www.cxybcw.com/14616.html

联系我们

13687733322

在线咨询:点击这里给我发消息

邮件:1877088071@qq.com

工作时间:周一至周五,9:30-18:30,节假日休息

QR code