Ch2 中间件 or Controller

(题前话:我本来一直以为这东西都叫controller的,结果被徐同学打脸表示在Node 的Express框架里统称中间件,所以这里就叫Controller好了)

什么是Controller

在上一话中,我们使用了简单的“hello world”作为响应,主要是测试我们的基础框架是否可用。但是,作为一个功能齐全的框架,我们怎么可能对所有的请求都返回”Hello world”呢。

所以,这里我们需要Controller出场了。

从基础概念上来说,Controller就是对 request 与response进行处理的模块。

在MVC中,M为data modeling,即与数据层通讯/抽象/封装,以及算法实现等的模块。V是View,就是设计师设计出的页面模板。C就是Controller,负责转发,处理请求。

Glance at Controller

首先,为了方便Lambda的拓展,以及未来框架的拓展,我们定义一个接口。

[code lang=”java”]
@FunctionalInterface
public interface IController {
// main logic

void execute(HttpServletRequest request,
HttpServletResponse response)
throws Exception;
}

[/code]

这个接口被FunctionalInterface标记修饰,而且只有一个方法,这样我们就可以用lambda表达式快速实现一个Controller对象。

With Route

有了Controller,下一个需要的就是Route。Route的作用,就是将用户请求的URL,如实的映射到预设好的Controller上。

所以,我们需要定义一个 Route接口,用来将URL与Controller结合在一起。

[code lang=”java”]

public interface Router {

void addController(String url,
IController controller,
String method)
throws Exception ;

IController route(HttpServletRequest request)
throws Exception;

List<String> routeRules();

}

[/code]

一般来说,URL 的格式类似于: [protocal name]://[hostname]/[path]/[to]/[resource]/{parameter}?{other_parameter}

在我的实现里,我利用了正则来做匹配。并保留{}圈住的内容作为传入参数。

[code lang=”java”]
private static Pattern variablesPattern =
Pattern.compile("\\$\\{(\\w+)\\}");
[/code]

这个语法就是用Java的正则库将大括号包住的变量“抽”出来处理。

匹配的代码如下:

[code lang=”java”]
@Override
public boolean match(HttpServletRequest request) {
Matcher matcher = pattern.matcher(request.getRequestURI());
if (matcher.matches()){
if (pathVariables != null && pathVariables.size() > 0){
for (int i = 0; i < matcher.groupCount(); ++i){
request.setAttribute(pathVariables.get(i), matcher.group(i + 1));
}
}
return true;
}
return false;
}
[/code]

(全部代码请翻阅 https://github.com/andysim3d/JxpressTutorial/tree/ch2 ,限于篇幅这里只有部分代码)

Bind

创建一个类,存放router 与 controller 实例各一。

[code lang=”java”]
public class MatchAndController {

private final UrlMatcher matcher;
private final IController controller;

public MatchAndController(UrlMatcher matcher, IController controller)
{ this.controller = controller
; this.matcher = matcher;
}

public UrlMatcher getMatcher(){
return matcher;
}

public IController getController(){
return controller;
}

}
[/code]

同时修改的还有JettyServer里面的list,之前我们存放的是静态url,现在,需要存放的是MatchAndController。

[code lang=”java”]
public class JettyServer implements WebServer {
//…
//change here
protected UrlRouter urlRouter = new UrlRout

//change here
public WebServer get(String url, IController ctl) {
try {
urlRouter.addController(url,ctl,"get");
return this;
}
catch (Exception exp){
// do nothing
}finally {
return this;
}
}

public WebServer start() {
//…
//change here
Handler hdlr = new WebServerHandler(urlRouter);
//…
}

private static class WebServerHandler extends AbstractHandler {
// change here
private UrlRouter urlRouter;
public WebServerHandler(UrlRouter urlRouter){
this.urlRouter = urlRouter;
}
public void handle(String s, Request request,
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse)
throws IOException, ServletException {
try{
process(httpServletRequest, httpServletResponse);
}
catch (Exception e){
e.printStackTrace();
}
}

private void process(HttpServletRequest request,
HttpServletResponse response)
throws IOException {
try {
urlRouter.route(request).
execute(request, response);;
}
catch(Exception e){
}
}
}
}
[/code]

Test

将入口方法写成如下形式:

[code lang=”java”]
public class Main {

public static void main(String[] args) {

WebServer server = new JettyServer();
server.get("/", (req, res)->{
OutputStreamWriter optwriter = new OutputStreamWriter(res.getOutputStream(), "utf-8");
optwriter.write("Hello You!");
res.setStatus(200);
optwriter.flush();
}).get("/test", (req, res)->{
OutputStreamWriter optwriter = new OutputStreamWriter(res.getOutputStream(), "utf-8");
optwriter.write("Hello There!");
res.setStatus(200);
optwriter.flush();
}).
listen(8080);
server.start();
}
}
[/code]

然后用浏览器分别访问 localhost:8080/ 与 localhost:8080/test

大功告成!

What did we do

简单来说,我们定义了一个叫做IController的接口,这个接口负责接收请求,并返回响应。

为了保证对应的请求能被正确的IController实例处理,我们又定义了一个Route的接口,它负责匹配定义好的路由与请求。

接下来,我们将IController与Route对象一对一绑定在一起,放在容器中(因为我们会有多个Route+IController)。在有请求进来的时候,我们遍历所有Route+IController对象,并试图匹配。(所以如果有多个可匹配项,那优先匹配第一个定义的)。若找到匹配项,则利用对应的 IController来处理请求。

And … then?

下一次教程,我们将要开始搞定中间件与异常处理这两个模块。

Andy

Andy

a lazy and busy guy.

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.