• Xitrum学习笔记04


    RESTful API:

    符合RESTful架构的API称为RESTful API,不同的前端设备与后端进行通信的一种统一机制

    什么是RESTful架构:

      (1)每一个URI代表一种资源;

      (2)客户端和服务器之间,传递这种资源的某种表现层;

      (3)客户端通过HTTP动词(GET用来获取资源,POST用来新建、更新资源,PUT用来更新资源,DELETE用来删除资源),对服务器端资源进行操作,实现"表现层状态转化"。

    REST -- REpresentational State Transfer
    全称是 Resource Representational State Transfer:通俗来讲就是:资源在网络中以某种表现形式进行状态转移。分解开来:
    Resource:资源,即数据(前面说过网络的核心)。比如 newsfeed,friends等;
    Representational:某种表现形式,比如用JSON,XML,JPEG等;
    State Transfer:状态变化。通过HTTP动词实现。

    参考文档:

    http://www.ruanyifeng.com/blog/2011/09/restful       --理解RESTful架构

    http://www.ruanyifeng.com/blog/2014/05/restful_api.html   --RESTful API 设计指南

    https://www.zhihu.com/question/28557115    

    Xitrum RESTful API示例:

    import xitrum.Action
    import xitrum.annotation.GET
    @GET("articles")
    class ArticlesIndex extends Action {
      def execute() {...}
    }
    @GET("articles/:id")
    class ArticlesShow extends Action {
      def execute() {...}
    }

    POST, PUT, PATCH, DELETE, and OPTIONS的使用与GET相同,Xitrum自动把HEAD当做响应体为空的GET来处理。

    对于不支持PUT和DELETE的HTTP客户端,通过发送响应体中带有 _method=put 和 _method=delete 的POST来模拟PUT和DELETE动作

    Web应用程序启动时,Xitrum会扫描所有annotations,创建路由表并在Console里打印出来,如:

    [INFO] Normal routes:
    GET        /articles/new                  demos.action.ArticlesNew
    GET        /                              demos.action.SiteIndex
    POST       /api/articles                  demos.action.ApiArticlesCreate
    PATCH      /api/articles/:id              demos.action.ApiArticlesUpdate
    DELETE     /api/articles/:id              demos.action.ApiArticlesDestroy
    GET        /articles/:id<[0-9]+>.:format  demos.action.ArticlesDotShow
    [INFO] SockJS routes:
    /sockJsChat         demos.action.SockJsChatActor    websocket: true, cookie_needed: false
    /fileMonitorSocket  demos.action.FileMonitorSocket  websocket: true, cookie_needed: false
    [INFO] Error routes:
    404  demos.action.NotFoundError
    500  demos.action.ServerError
    [INFO] Xitrum routes:
    GET  /xitrum/xitrum-3.28.3.js  xitrum.js
    GET  /xitrum/swagger.json      xitrum.routing.SwaggerJson
    GET  /xitrum/swagger           xitrum.routing.SwaggerUi
    GET  /xitrum/metrics/viewer    xitrum.metrics.XitrumMetricsViewer
    [INFO] Xitrum SockJS routes:
    /xitrum/metrics/channel  xitrum.metrics.XitrumMetricsChannel  websocket: true, cookie_needed: false

    路由(Routes)会自动被收集,不需要额外的声明工作,我们也可以以类型安全的方式重建URLs

    Route cache(路由缓存)

    为了加快启动速度,路由被缓存到了文件 routes.cache中。在开发过程中,在target目录下的.class中的路由不会被缓存。

    如果改变了包含路由的依赖库,需要删除routes.cache。routes.cache不应该被提交到代码版本库中

    使用First和Last定义Route优先级

    import xitrum.annotation.{GET, First}
    @GET("articles/:id")
    class ArticlesShow extends Action {
      def execute() {...}
    }
    
    @First // This route has higher priority than "ArticlesShow" above
    @GET("articles/new")
    class ArticlesNew extends Action {
      def execute() {...}
    }

    这样在定义routes表时,ArticlesNew相应的路由就会排到最前面。Last注解的使用与First相同

    一个Action有多个路由

    @GET("image", "image/:format")
    class Image extends Action {
      def execute() {
        val format = paramo("format").getOrElse("png")
        // ...
      }
    }

    路由中有点号和正则表达式

    @GET("articles/:id", "articles/:id.:format")
    class ArticlesShow extends Action {
      def execute() {
        val id = param[Int]("id")
        val format = paramo("format").getOrElse("html")
        // ...
      }
    }
    
    @GET("articles/:id<[0-9]+>")
    ...

    获取其余路由

    / 斜杠是特殊字符,所以不能出现在路由的参数中。需要参数中有斜杠的话,要把参数放在最后,且要用星号,例如

    GET("service/:id/proxy/:*")

    这个写法可以匹配  /service/123/proxy/http://foo.com/bar

    获取 :* 的部分可以用以下代码实现

    val url = param("*")   // Will be "http://foo.com/bar"

    通过超链接标记<a>链接到一个Action

    在View中的写法

    <a href={url[ArticlesShow]("id" -> myArticle.id)}>{myArticle.title}</a>

    重定向和转发到另一个action

    重定向和转发:

    转发是服务器行为,重定向是客户端行为。为什么这样说呢,这就要看两个动作的工作流程:

    转发过程:客户浏览器发送http请求——》web服务器接受此请求——》调用内部的一个方法在容器内部完成请求处理和转发动作——》将目标资源发送给客户;在这里,转发的路由必须是同一个web容器下的url,其不能转向到其他的web路由上去,中间传递的是自己的容器内的request。在客户浏览器地址栏显示的仍然是其第一次访问的路由,也就是说客户是感觉不到服务器做了转发的。转发行为是浏览器只做了一次访问请求。

    重定向过程:客户浏览器发送http请求——》web服务器接受后发送302状态码响应及对应新的location给客户浏览器——》客户浏览器发现是302响应,则自动再发送一个新的http请求,请求url是新的location地址——》服务器根据此请求寻找资源并发送给客户。在这里location可以重定向到任意URL,既然是浏览器重新发出了请求,则就没有什么request传递的概念了。在客户浏览器地址栏显示的是其重定向的路由,客户可以观察到地址的变化的。重定向行为是浏览器做了至少两次的访问请求的。

    典型的应用场景:
    1. forward: 访问 Servlet 处理业务逻辑,然后 forward 到 jsp 显示处理结果,浏览器里 URL 不变
    2. redirect: 提交表单,处理成功后 redirect 到另一个 jsp,防止表单重复提交,浏览器里 URL 变了

    Xitrum的重定向:

    import xitrum.Action
    import xitrum.annotation.{GET, POST}
    @GET("login")
    class LoginInput extends Action {
      def execute() {...}
    }
    @POST("login")
    class DoLogin extends Action {
      def execute() {
        ...
        // After login success
        redirectTo[AdminIndex]()
      }
    }
    @GET("admin")
    class AdminIndex extends Action {
      def execute() {
        ...
        // Check if the user has not logged in, redirect him to the login page
        redirectTo[LoginInput]()  //产生新的request
      }
    }

    重定向到当前action可以用 redirectToThis() 方法

    转发到另一个action

    forwardTo[AnotherAction]()   //不产生新的request

    如何确定需求是否是一个Ajax需求

    用 isAjax

    // In an action
    val msg = "A message"
    if (isAjax)
      jsRender("alert(" + jsEscape(msg) + ")")
    else
      respondText(msg)

    CSRF相关

    什么事CSRF

    Cross-site request forgery,跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF。

    危害是在用户登录受信任网站A,并在本地生成Cookie后,在不登出A的情况下,访问危险网站B。

    这样攻击者可以盗用你的身份,以你的名义发送在网站A上的恶意请求。比如可以盗取你的账号,以你的身份发送邮件,购买商品等。

    防御CSRF

    对于非GET请求, Xitrum默认为web应用防御CSRF

    在View代码中加入 antiCsrfMeta 后,

    import xitrum.Action
    import xitrum.view.DocType
    trait AppAction extends Action {
      override def layout = DocType.html5(
        <html>
          <head>
            {antiCsrfMeta}
            {xitrumCss}
            {jsDefaults}
            <title>Welcome to Xitrum</title>
          </head>
          <body>
            {renderedView}
            {jsForView}
           </body>
        </html>
      )
    }

    相应地在HTML页面的head内,会生成

    <meta name="csrf-token" content="5402330e-9916-40d8-a3f4-16b271d583be" />

    如果将xitrum.js加到View模板,这个token会被自动包含到所有非GET Ajax请求中,作为由jQuery发出的X-CSRF-Token头信息。 

    View中调用jsDefaults方法,就可以把xitrum.js加入到view中。

    另一种把xitrum.js加入到view中的方法是,在View中加入如下代码

    <script type="text/javascript" src={url[xitrum.js]}></script>

    Xitrum从X-CSRF-Token请求头中获取CSRF token,如果这个头不存在,Xitrum从csrf-token请求体参数中获取(不是从URL中的参数获取)

    如果head中没有使用csrf-token meta标签和xitrum.js,在form中需要添加antiCsrfInput或antiCsrfToken

    form(method="post" action={url[AdminAddGroup]})
      != antiCsrfInput
    //或者
    form(method="post" action={url[AdminAddGroup]})
      input(type="hidden" name="csrf-token" value={antiCsrfToken})

    需要跳过CSRF检查时,混入特质 xitrum.SkipCsrfCheck到Action中,如

    import xitrum.{Action, SkipCsrfCheck}
    import xitrum.annotation.POST
    trait Api extends Action with SkipCsrfCheck
    @POST("api/positions")
    class LogPositionAPI extends Api {
      def execute() {...}
    }
    @POST("api/todos")
    class CreateTodoAPI extends Api {
      def execute() {...}
    }

    变更已收集的路由

    Xitrum在Web应用启动时自动收集Action路由,可以通过在Boot.scala中使用xitrum.Config.routes变更路由

    import xitrum.{Config, Server}
    object Boot {
      def main(args: Array[String]) {
        // You can modify routes before starting the server
        val routes = Config.routes
    
        // Remove routes to an action by its class
        routes.removeByClass[MyClass]()
        if (demoVersion) {
          // Remove routes to actions by a prefix
          routes.removeByPrefix("premium/features")
          // This also works
          routes.removeByPrefix("/premium/features")
        }
        ...
        Server.start()
      }
    }

    获取整个请求内容

    一般情况下,如果请求的内容类型不是application/x-www-form-urlencoded,可能需要在代码中解析整个请求内容

    //To get it as a string:
    val body = requestContentString
    
    //To get it as JSON:
    val myJValue = requestContentJValue // => JSON4S (http://json4s.org) JValue
    val myMap = xitrum.util.SeriDeseri.fromJValue[Map[String, Int]](myJValue)

    如果想获得全面控制,使用 request.getContent,它返回一个ByteBuf类型的值(查阅了http://netty.io/4.0/api/io/netty/handler/codec/http/FullHttpRequest.html,没有getContent方法,这句话有错误

  • 相关阅读:
    工作交接
    .NET 利用反射将对象数据添加到数据库
    【C#】IPAddress.Any 解决本地ip和服务器ip切换问题
    【C#】警告System.Configuration.ConfigurationSettings.AppSettings" 已过时
    【C#】TcpListener的对象“已过时”的编译警告
    大城小胖这几年积累的动画库、手势库、物理引擎库
    select option项选择后跳转页面
    360引起的Soap的java.io.EOFException错误
    linux安装mysql-5.7.22与数据自动备份
    性能测试流程
  • 原文地址:https://www.cnblogs.com/sunspeedzy/p/6855095.html
Copyright © 2020-2023  润新知