新闻资讯

    【CSDN 编者按】大家都知道Web和API服务器在互联网中的重要性,在计算机网络方面提供了最基本的界面。本文主要介绍了怎样利用Scala实现实时聊天网站和API服务器,通过本篇文章,你定将受益匪浅。

    作者 | Haoyi译者 |弯月,责编 | 刘静出品 | CSDN(ID:)

    以下为译文:

    Web和API服务器是互联网系统的骨干,它们为计算机通过网络交互提供了基本的界面,特别是在不同公司和组织之间。这篇指南将向你介绍如何利用Scala简单的HTTP服务器,来提供Web内容和API。本文还会介绍一个完整的例子,告诉你如何构建简单的实时聊天网站,同时支持HTML网页和JSON API端点。

    这篇文及章的目的是介绍怎样用Scala实现简单的HTTP服务器,从而提供网页服务,以响应API请求。我们会建立一个简单的聊天网站,可以让用户发表聊天信息,其他访问网站的用户都可以看见这些信息。为简单起见,我们将忽略认证、性能、用户挂历、数据库持久存储等问题。但是,这篇文章应该足够你开始用Scala构建网站和API服务器了,并为你学习并构建更多产品级项目打下基础。

    我们将使用Cask web框架:

    Cask是一个Scala的HTTP为框架,可以用来架设简单的网站并迅速运行。

    开始

    要开始使用Cask,只需下载并解压示例程序:

    $ curl -L https://github.com/lihaoyi/cask/releases/download/0.3.0/minimalApplication-0.3.0.zip > cask.zip

    $ unzip cask.zip

    $ cd minimalApplication-0.3.0

    运行find来看看有哪些文件:

    $ find . -type f
    ./build.sc
    ./app/test/src/ExampleTests.scala
    ./app/src/MinimalApplication.scala
    ./mill

    我们感兴趣的大部分代码都位于app/src/.scala中。

    package app
    object MinimalApplication extends cask.MainRoutes{
      @cask.get("/")
      def hello() = {
        "Hello World!"
      }

      @cask.post("/do-thing")
      def doThing(request: cask.Request) = {
        new String(request.readAllBytes()).reverse
      }

      initialize()
    }

    用build.sc进行构建:

    html提交表单到服务器_html select 选中提交表单_表单提交 html

    import mill._, scalalib._

    object app extends ScalaModule{
      def scalaVersion = "2.13.0"
      def ivyDeps = Agg(
        ivy"com.lihaoyi::cask:0.3.0"
      )

      object test extends Tests{
        def testFrameworks = Seq("utest.runner.Framework")

        def ivyDeps = Agg(
          ivy"com.lihaoyi::utest::0.7.1",
          ivy"com.lihaoyi::requests::0.2.0",
        )

      }
    }

    如果你使用,那么可以运行如下命令来设置项目配置:

    $ ./mill mill.scalalib.GenIdea/idea

    现在你可以在中打开-0.3.0/目录html提交表单到服务器,查看项目的目录,也可以进行编辑。

    可以利用Mill构建工具运行该程序,只需执行./mill:

    $ ./mill -w app.runBackground

    该命令将在后台运行Cask Web服务器,同时监视文件系统,如果文件发生了变化,则重启服务器。然后我们可以使用浏览器浏览服务器,默认网址是:8080:

    在/do-thing上还有个POST端点,可以在另一个终端上使用curl来访问:

    $ curl -X POST --data hello http://localhost:8080/do-thing
    olleh

    可见,它接受数据hello,然后将反转的字符串返回给客户端。

    然后可以运行app/test/src/.scala中的自动化测试:

    $ ./mill clean app.runBackground # stop the webserver running in the background

    $ ./mill app.test
    [50/56] app.test.compile
    [info] Compiling 1 Scala source to /Users/lihaoyi/test/minimalApplication-0.3.0/out/app/test/compile/dest/classes ...
    [info] Done compiling.
    [56/56] app.test.test
    -------------------------------- Running Tests --------------------------------
    + app.ExampleTests.MinimalApplication 629ms

    现在基本的东西已经运行起来了,我们来重新运行Web服务器:

    $ ./mill -w app.runBackground

    html select 选中提交表单_html提交表单到服务器_表单提交 html

    然后开始实现我们的聊天网站!

    提供HTML服务

    第一件事就是将纯文本的"Hello, World!"转换成HTML网页。最简单的方式就是利用这个HTML生成库。要在项目中使用,只需将其作为依赖项加入到build.sc文件即可:

      def ivyDeps = Agg(
    +    ivy"com.lihaoyi::scalatags:0.7.0",   
         ivy"com.lihaoyi::cask:0.3.0"
       )

    如果使用,那么还需要重新运行./mill mill../idea命令,来发现依赖项的变动,然后重新运行./mill -w app.让Web服务器重新监听改动。

    然后,我们可以在.scala中导入:

    package app
    +import scalatags.Text.all._
     object MinimalApplication extends cask.MainRoutes{

    然后用一段最简单的 HTML模板替换"Hello, World!"。

     def hello() = {
    -    "Hello World!"
    +    html(
    +      head(),
    +      body(
    +        h1("Hello!"),
    +        p("World")
    +      )
    +    ).render
       }

    我们应该可以看到./mill -w app.命令重新编译了代码并重启了服务器。然后刷新网页额,就会看到纯文本已经被替换成HTML页面了。

    为了让页面更好看一些,我们使用这个CSS框架。只需按照它的指南,使用link标签引入:

         head(
    +        link(
    +          rel := "stylesheet", 
    +          href := "https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
    +        )
           ),

    html select 选中提交表单_html提交表单到服务器_表单提交 html

      body(
    -        h1("Hello!"),
    -        p("World")
    +        div(cls := "container")(
    +          h1("Hello!"),
    +          p("World")
    +        )
           )

    现在字体不太一样了:

    虽然还不是最漂亮的网站,但现在已经足够了。

    在本节的末尾,我们修改一下的HTML模板,加上硬编码的聊天文本和假的输入框,让它看起来更像一个聊天应用程序。

     body(
             div(cls := "container")(
    -          h1("Hello!"),
    -          p("World")
    +          h1("Scala Chat!"),
    +          hr,
    +          div(
    +            p(b("alice"), " ""Hello World!"),
    +            p(b("bob"), " ""I am cow, hear me moo"),
    +            p(b("charlie"), " ""I weigh twice as much as you")
    +          ),
    +          hr,
    +          div(
    +            input(`type` := "text", placeholder := "User name", width := "20%"),
    +            input(`type` := "text", placeholder := "Please write a message!", width := "80%")
    +          )
             )
           )

    现在我们有了一个简单的静态网站,其利用Cask web框架和 HTML库提供HTML网页服务。现在的服务器代码如下所示:

    package app
    import scalatags.Text.all._
    object MinimalApplication extends cask.MainRoutes{
      @cask.get("/")
      def hello() = {
        html(
          head(
            link(
              rel := "stylesheet",
              href := "https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
            )
          ),
          body(
            div(cls := "container")(
              h1("Scala Chat!"),
              hr,
              div(
                p(b("alice"), " ""Hello World!"),
                p(b("bob"), " ""I am cow, hear me moo"),
                p(b("charlie"), " ""I weigh twice as much as you")
              ),
              hr,
              div(
                input(`type` := "text", placeholder := "User name", width := "20%"),
                input(`type` := "text", placeholder := "Please write a message!", width := "80%")
              )
            )
          )
        ).render
      }

      initialize()
    }

    接下来,我们来看看怎样让它支持交互!

    表单和数据

    为网站添加交互的第一次尝试是使用HTML表单。首先我们要删掉硬编码的消息列表,转而根据数据来输出HTML网页:

     object MinimalApplication extends cask.MainRoutes{
    +  var messages = Vector(
    +    ("alice""Hello World!"),
    +    ("bob""I am cow, hear me moo"),
    +    ("charlie""I weigh twice as much as you"),
    +  )
      @cask.get("/")

     div(
    -            p(b("alice"), " ""Hello World!"),
    -            p(b("bob"), " ""I am cow, hear me moo"),
    -            p(b("charlie"), " ""I weight twice as much as you")
    +            for((name, msg) <- messages)
    +            yield p(b(name), " ", msg)
               ),

    这里我们简单地使用了内存上的存储。关于如何将消息持久存储到数据库中,我将在以后的文章中介绍。

    html提交表单到服务器_表单提交 html_html select 选中提交表单

    接下来,我们需要让页面底部的两个input支持交互。为实现这一点,我们需要将它们包裹在form元素中:

        hr,
    -          div(
    -            input(`type` := "text", placeholder := "User name", width := "20%"),
    -            input(`type` := "text", placeholder := "Please write a message!", width := "80%")
    +          form(action := "/", method := "post")(
    +            input(`type` := "text", name := "name", placeholder := "User name", width := "20%"),
    +            input(`type` := "text", name := "msg", placeholder := "Please write a message!", width := "60%"),
    +            input(`type` := "submit", width := "20%")
              )

    这样我们就有了一个可以交互的表单,外观跟之前的差不多。但是,提交表单会导致Error 404: Not Found错误。这是因为我们还没有将表单与服务器连接起来,来处理表单提交并获取新的聊天信息。我们可以这样做:

       -  )
    +
    +  @cask.postForm("/")
    +  def postHello(name: String, msg: String) = {
    +    messages = messages :+ (name -> msg)
    +    hello()
    +  }
    +
       @cask.get("/")

    @cast.定义为根URL(即 / )添加了另一个处理函数,但该处理函数处理POST请求,而不处理GET请求。Cask文档()中还有关于@cask.*注释的其他例子,你可以利用它们来定义处理函数。

    验证

    现在,用户能够以任何名字提交任何评论。但是,并非所有的评论和名字都是有效的:最低限度,我们希望保证评论和名字字段非空,同时我们还需要限制最大长度。

    实现这一点很简单:

      @cask.postForm("/")
       def postHello(name: String, msg: String) = {
    -    messages = messages :+ (name -> msg)
    +    if (name != "" && name.length < 10 && msg != "" && msg.length < 160){
    +      messages = messages :+ (name -> msg)
    +    }
         hello()
       }

    这样就可以阻止用户输入非法的name和msg,但出现了另一个问题:用户输入了非法的名字或信息并提交,那么这些信息就会消失,而且不会为错误产生任何反馈。解决方法是,给hello()页面渲染一个可选的错误信息,用它来告诉用户出现了什么问题:

     @cask.postForm("/")
       def postHello(name: String, msg: String) = {
    -    if (name != "" && name.length < 10 && msg != "" && msg.length < 160){
    -      messages = messages :+ (name -> msg)
    -    }
    -     hello()
    +    if (name == "") hello(Some("Name cannot be empty"))
    +    else if (name.length >= 10) hello(Some("Name cannot be longer than 10 characters"))
    +    else if (msg == "") hello(Some("Message cannot be empty"))
    +    else if (msg.length >= 160) hello(Some("Message cannot be longer than 160 characters"))
    +    else {
    +      messages = messages :+ (name -> msg)
    +      hello()
    +    }
       }

      @cask.get("/")
    -  def hello() = {
    +  def hello(errorOpt: Option[String] = None) = {
         html(

      hr,
    +          for(error <- errorOpt) 
    +          yield i(color.red)(error),
               form(action := "/", method := "post")(

    html select 选中提交表单_html提交表单到服务器_表单提交 html

    现在,当名字或信息非法时,就可以正确地显示出错误信息了。

    下一次提交时错误信息就会消失。

    记住名字和消息

    现在比较烦人的是,每次向聊天室中输入消息时html提交表单到服务器,都要重新输入用户名。此外,如果用户名或信息非法,那消息就会被清除,只能重新输入并提交。可以让hello页面处理函数来填充这些字段,这样就可以解决:

     @cask.get("/")
    -  def hello(errorOpt: Option[String] = None) = {
    +  def hello(errorOpt: Option[String] = None, 
    +            userName: Option[String] = None,
    +            msg: Option[String] = None) = {
         html(

      form(action := "/", method := "post")(
    -            input(`type` := "text", name := "name", placeholder := "User name", width := "20%", userName.map(value := _)),
    -            input(`type` := "text", name := "msg", placeholder := "Please write a message!", width := "60%"),
    +            input(
    +              `type` := "text"
    +              name := "name"
    +              placeholder := "User name"
    +              width := "20%"
    +              userName.map(value := _)
    +            ),
    +            input(
    +              `type` := "text",
    +              name := "msg",
    +              placeholder := "Please write a message!"
    +              width := "60%",
    +              msg.map(value := _)
    +            ),
                 input(`type` := "submit", width := "20%")

    这里我们使用了可选的和msg查询参数,如果它们存在,则将其作为HTML input标签的value的默认值。

    接下来在的处理函数中渲染页面时,填充和msg,再发送给用户:

      def postHello(name: String, msg: String) = {
    -    if (name == "") hello(Some("Name cannot be empty"))
    -    else if (name.length >= 10) hello(Some("Name cannot be longer than 10 characters"))
    -    else if (msg == "") hello(Some("Message cannot be empty"))
    -    else if (msg.length >= 160) hello(Some("Message cannot be longer than 160 characters"))
    +    if (name == "") hello(Some("Name cannot be empty"), Some(name), Some(msg))
    +    else if (name.length >= 10) hello(Some("Name cannot be longer than 10 characters"), Some(name), Some(msg))
    +    else if (msg == "") hello(Some("Message cannot be empty"), Some(name), Some(msg))
    +    else if (msg.length >= 160) hello(Some("Message cannot be longer than 160 characters"), Some(name), Some(msg))
         else {
           messages = messages :+ (name -> msg)
    -      hello()
    +      hello(None, Some(name), None)
         }

    注意任何情况下我们都保留name,但只有错误的情况才保留msg。这样做是正确的,因为我们只希望用户在出错时才进行编辑并重新提交。

    完整的代码.scala如下所示:

      package app
    import scalatags.Text.all._
    object MinimalApplication extends cask.MainRoutes{
      var messages = Vector(
        ("alice""Hello World!"),
        ("bob""I am cow, hear me moo"),
        ("charlie""I weigh twice as you"),
      )

      @cask.postForm("/")
      def postHello(name: String, msg: String) = {
        if (name == "") hello(Some("Name cannot be empty"), Some(name), Some(msg))
        else if (name.length >= 10) hello(Some("Name cannot be longer than 10 characters"), Some(name), Some(msg))
        else if (msg == "") hello(Some("Message cannot be empty"), Some(name), Some(msg))
        else if (msg.length >= 160) hello(Some("Message cannot be longer than 160 characters"), Some(name), Some(msg))
        else {
          messages = messages :+ (name -> msg)
          hello(None, Some(name), None)
        }
      }

      @cask.get("/")
      def hello(errorOpt: Option[String] = None,
                userName: Option[String] = None,
                msg: Option[String] = None) = {
        html(
          head(
            link(
              rel := "stylesheet",
              href := "https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
            )
          ),
          body(
            div(cls := "container")(
              h1("Scala Chat!"),
              hr,
              div(
                for((name, msg) <- messages)
                yield p(b(name), " ", msg)
              ),
              hr,
              for(error <- errorOpt)
              yield i(color.red)(error),
              form(action := "/", method := "post")(
                input(
                  `type` := "text",
                  name := "name",
                  placeholder := "User name",
                  width := "20%",
                  userName.map(value := _)
                ),
                input(
                  `type` := "text",
                  name := "msg",
                  placeholder := "Please write a message!",
                  width := "60%",
                  msg.map(value := _)
                ),
                input(`type` := "submit", width := "20%")
              )
            )
          )
        ).render
      }

      initialize()
    }

    利用Ajax实现动态页面更新

    现在有了一个简单的、基于表单的聊天网站,用户可以发表消息,其他用户加载页面即可看到已发表的消息。下一步就是让网站变成动态的,这样用户不需要刷新页面就能发表消息了。

    为实现这一点,我们需要做两件事情:

网站首页   |    关于我们   |    公司新闻   |    产品方案   |    用户案例   |    售后服务   |    合作伙伴   |    人才招聘   |   

地址:北京市海淀区    电话:010-     邮箱:@126.com

备案号:冀ICP备2024067069号-3 北京科技有限公司版权所有