基于 Servlet 的微路由和控制器实现。


README

Latest Stable Version Total Downloads License Build Status Scrutinizer Code Quality Code Coverage

简介

Rout.Lt 2 提供了一个小巧但非常快速的基于 servlet 的路由和控制器实现,适用于 appserver.io。

安装

如果您想编写一个使用 Rout.Lt 2 的应用程序,您必须使用 Composer 进行安装。为此,只需将其添加到您的 composer.json 文件中的依赖项即可。

{
    "require": {
        "appserver-io/routlt": "~2.0"
    }
}

配置

Rout.Lt 2 自带其自身的注解和配置文件。

应用

因此,在应用程序启动时初始化这些注解以及所有其他框架特定配置文件是必要的。为此,您需要提供一个自定义的 META-INF/context.xml 文件,该文件包含您的应用程序,并且需要对象管理配置的 <descriptors/> 节点。

<?xml version="1.0" encoding="UTF-8"?>
<context
        name="routlt"
        type="AppserverIo\Appserver\Application\Application"
        xmlns="http://www.appserver.io/appserver">
    <managers>
        ...
        <manager
            name="ObjectManagerInterface"
            type="AppserverIo\Appserver\DependencyInjectionContainer\ObjectManager"
            factory="AppserverIo\Appserver\DependencyInjectionContainer\ObjectManagerFactory">
            <descriptors>
                <descriptor>AppserverIo\Description\ServletDescriptor</descriptor>
                <descriptor>AppserverIo\Description\MessageDrivenBeanDescriptor</descriptor>
                <descriptor>AppserverIo\Description\StatefulSessionBeanDescriptor</descriptor>
                <descriptor>AppserverIo\Description\SingletonSessionBeanDescriptor</descriptor>
                <descriptor>AppserverIo\Description\StatelessSessionBeanDescriptor</descriptor>
                <descriptor>AppserverIo\Routlt\Description\PathDescriptor</descriptor>
            </descriptors>
        </manager>
        ...
    </managers>
</context>

Servlet 引擎

由于 Rout.Lt 2 基于Servlet,您还需要为您的应用程序提供一个 WEB-INF/web.xml

假设您已将 appserver.io 安装在 Linux/Mac OS X 的 /opt/appserver 目录下,并且您的应用程序名为 myapp,您将保存以下内容的 web.xml 文件在 /opt/appserver/myapp/WEB-INF 目录中。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://www.appserver.io/appserver">

    <display-name>appserver.io example application</display-name>
    <description>This is the example application for the appserver.io servlet engine.</description>

    <session-config>
        <session-name>my_login</session-name>
        <session-file-prefix>my_session_</session-file-prefix>
    </session-config>

    <servlet>
        <description>A servlet that handles DHTML files.</description>
        <display-name>The DHTML servlet</display-name>
        <servlet-name>dhtml</servlet-name>
        <servlet-class>AppserverIo\Appserver\ServletEngine\Servlets\DhtmlServlet</servlet-class>
    </servlet>
    
    <servlet>
        <description>The Rout.Lt 2 controller servlet implementation.</description>
        <display-name>The Rout.Lt 2 controller servlet</display-name>
        <servlet-name>routlt</servlet-name>
        <servlet-class>AppserverIo\Routlt\ControllerServlet</servlet-class>
        <!-- 
         | this is mandatory and specifies the path where Rout.Lt
         | is looking for your action class implementations
         |-->
        <init-param>
            <param-name>action.namespace</param-name>
            <param-value>/MyApp/Actions</param-value>
        </init-param>
        <!-- 
         | this is optional and can be used to store credentials
         | you don't want to add to version control, for example
         |-->
        <init-param>
            <param-name>routlt.configuration.file</param-name>
            <param-value>WEB-INF/routlt.properties</param-value>
        </init-param>
    </servlet>

    <servlet-mapping>
        <servlet-name>dhtml</servlet-name>
        <url-pattern>*.dhtml</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
        <servlet-name>routlt</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
        <servlet-name>routlt</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

</web-app>

动作映射

由于 Rout.Lt 2 提供了用于配置路由和动作的注解,因此版本 ~1.0 所需的 routlt.json 配置文件不再必要也不受支持。

使用注解

您有两个注解,即 @Path@Action,用于配置应用程序的路由。这些注解允许您将请求的 Path Info 映射到动作类中的方法。这种机制被许多可用的框架采用。路径段将由斜线分隔。第一个段必须映射到 @Path 注解的 name 属性的值,第二个段映射到 @Action 注解的一个方法。

例如,假设您想派发 URL http://127.0.0.1:9080/myapp/index.do/index/login,您需要一个类似于以下内容的动作类实现。

namespace MyApp\Actions;

use AppserverIo\Routlt\DispatchAction;
use TechDivision\Servlet\Http\HttpServletRequestInterface;
use TechDivision\Servlet\Http\HttpServletResponseInterface;

/**
 * Example action that shows the usage of the @Path annotation.
 *
 * @Path
 */
class IndexAction extends DispatchAction
{

    /**
     * Dummy action implementation that writes 'Hello World' to the response.
     *
     * @param \TechDivision\Servlet\Http\HttpServletRequestInterface  $servletRequest  The request instance
     * @param \TechDivision\Servlet\Http\HttpServletResponseInterface $servletResponse The response instance
     *
     * @return void
     */
    public function indexAction(HttpServletRequestInterface $servletRequest, HttpServletResponseInterface $servletResponse)
    {
        $servletResponse->appendBodyStream('Hello World!');
    }

    /**
     * Dummy action that shows the usage of the @Action annotation.
     *
     * @param \TechDivision\Servlet\Http\HttpServletRequestInterface  $servletRequest  The request instance
     * @param \TechDivision\Servlet\Http\HttpServletResponseInterface $servletResponse The response instance
     *
     * @return void
     * @Action(name="/login")
     */
    public function loginToBackendAction(HttpServletRequestInterface $servletRequest, HttpServletResponseInterface $servletResponse)
    {
        // do some login stuff here
    }
}

将上述代码保存到名为 /opt/appserver/webapps/myapp/WEB-INF/classes/MyApp/Actions/IndexAction.php 的文件中,并重启后,使用您喜欢的浏览器打开 URL http://127.0.0.1:9080/myapp/index.do/index。您应该在那里看到 Hello World!

如果不指定 name 属性,根据注解的不同,Rout.Lt 会使用类名或方法名。由于必须去掉 Action 后缀,因此动作和动作方法始终以 Action 结尾,而不能是其他。

通配符

从版本 2.2 开始,您还可以在 @Action 注解的 name 属性中使用通配符,例如。

/**
 * Action to fetch an articles relation and return the result, e. g. as JSON API compatible result.
 *
 * @param \TechDivision\Servlet\Http\HttpServletRequestInterface  $servletRequest  The request instance
 * @param \TechDivision\Servlet\Http\HttpServletResponseInterface $servletResponse The response instance
 *
 * @return void
 * @Action(name="/articles/:id/:relation")
 */
public function nameDoesntMatterAction(HttpServletRequestInterface $servletRequest, HttpServletResponseInterface $servletResponse)
{
    // fetch an articles relation, e. g the author and return's the result
}

通配符映射的值可以通过 $servletRequest->getParameter() 方法作为通常的请求参数使用,例如。

/**
 * Action to fetch an articles relation and return the result, e. g. as JSON API compatible result.
 *
 * @param \TechDivision\Servlet\Http\HttpServletRequestInterface  $servletRequest  The request instance
 * @param \TechDivision\Servlet\Http\HttpServletResponseInterface $servletResponse The response instance
 *
 * @return void
 * @Action(name="/articles/:id/:relation")
 */
public function nameDoesntMatterAction(HttpServletRequestInterface $servletRequest, HttpServletResponseInterface $servletResponse)
{

    // load ID and relation
    $id = $servletRequest->getParameter('id', FILTER_VALIDATE_INT);
    $relation = $servletRequest->getParameter('relation');
    
    // fetch an articles relation, e. g the author and return's the result
}

限制

除了通配符之外,您还可以为每个通配符定义限制,例如

/**
  * @Action(name="/articles/:id/:relation", restrictions={{"id", "\d+"}, {"relation", "\w+"}})
  */

如果定义了限制,则只有当限制符合时,变量才会被映射。限制背后的功能使用正则表达式来确保传递的值符合。因此,每个限制都包含通配符名称和限制条件本身,例如 {"id", "\d+"}。限制条件必须是一个有效的正则表达式,例如 \d+,以确保值必须是数字。如果没有设置限制,Rout.Lt 假定传递的值是字符串,并使用默认的 \w+ 条件来检查一致性。

默认值

除了限制外,还可以为通配符定义默认值,例如

/**
  * @Action(
  *     name="/articles/:id/:relation", 
  *     restrictions={{"id", "\d+"}, {"relation", "\w+"}}, 
  *     defaults={{"relation", "author"}}
  * )
  */

如果路径信息中没有给出关系,例如 /articles/3,则将设置默认值 author 并作为请求参数提供。

操作 -> 请求方法映射

有时需要允许仅对选定的请求方法调用操作。例如,可能需要配置 indexAction() 仅在 POST 请求上调用。为此,您可以将 @Post 注解添加到方法文档块中,如下所示

/**
 * Dummy action implementation that writes 'Hello World' to the response.
 *
 * @param \TechDivision\Servlet\Http\HttpServletRequestInterface  $servletRequest  The request instance
 * @param \TechDivision\Servlet\Http\HttpServletResponseInterface $servletResponse The response instance
 *
 * @return void
 * @Post
 */
public function indexAction(HttpServletRequestInterface $servletRequest, HttpServletResponseInterface $servletResponse)
{
    $servletResponse->appendBodyStream('Hello World!');
}

对于所有请求方法 CONNECTDELETEGETHEADOPTIONSPOSTPUTPATCHTRACE,都可用注解。如果您没有添加上述注解之一,则操作将在 所有 请求方法上调用。

如果您添加了其中之一,则操作将仅在注解上调用。在其他所有请求方法上,将抛出具有 404 状态码的 AppserverIo\Psr\Servlet\ServletException,这将导致出现 404 错误页面。

结果

通过使用 @Results 注解指定结果,开发人员可以指定后处理器来处理操作结果,例如通过模板引擎。默认情况下,Rout.Lt 2 提供了一个使用简单的 DHTML 文件作为模板并在 servlet 的 process() 方法范围内处理它们的 servlet。这允许访问 servlet 请求/响应实例以及 servlet 配置参数。

以下示例使用一个包含嵌套 @Result 注解的 @Results 注解,用于在调用 indexAction() 方法后处理 /path/to/my_template.dhtml

namespace MyApp\Actions;

use AppserverIo\Routlt\DispatchAction;
use AppserverIo\Routlt\ActionInterface;
use TechDivision\Servlet\Http\HttpServletRequestInterface;
use TechDivision\Servlet\Http\HttpServletResponseInterface;

/**
 * Example action that shows the usage of the @Results annotation.
 *
 * @Path
 * @Results({
 *     @Result(name="input", type="AppserverIo\Routlt\Results\ServletDispatcherResult", result="/path/to/my_template.dhtml")
 * })
 */
class IndexAction extends DispatchAction
{

    /**
     * Dummy action implementation that writes 'Hello World' to the response.
     *
     * @param \TechDivision\Servlet\Http\HttpServletRequestInterface  $servletRequest  The request instance
     * @param \TechDivision\Servlet\Http\HttpServletResponseInterface $servletResponse The response instance
     *
     * @return string The result identifier
     */
    public function indexAction(HttpServletRequestInterface $servletRequest, HttpServletResponseInterface $servletResponse)
    {
        try {
            // add the Hello World! text to the request attributes
            $servletRequest->setAttribute('text', 'Hello World!');
        } catch (\Exception $e) {
            // add the exception to the error messages
            $this->addFieldError('fatal', $e->getMessage());
        }
        // return the result identifier for our template
        return ActionInterface::INPUT;
    }
}

可以指定所需数量的 @Result 注解以支持不同场景中的结果处理。必须使用哪个结果取决于您的操作返回的字符串值。

必须存储在上述指定的 <PATH-TO-WEBAPP>/path/to/my_template.dhtml 下的 DHTML 文件将加载在 indexAction() 方法中添加为请求属性的字符串,并将其渲染为 HTML 文档。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Simple DHTML Servlet</title>
    </head>
    <body>
        <p><?php echo $servletRequest->getAttribute('text') ?></p>
    </body>
</html>

限制

Rout.Lt 2 以注解作为唯一的配置选项开始。没有方法可以使用部署描述符配置操作。已创建问题 #21 来寻找解决方案。

外部链接

  • 有关 appserver.io 的所有信息,请访问 appserver.io