文章

Jetty 教程 | 嵌入式Jetty服务器开发

1.Jetty简介

1.1 什么是Jetty

Jetty是一个提供 HTP服务器、HTTP客户端和javax.servlet容器的开源项目。

Jetty9 是Jetty的最近一个版本且比之前的版本有很大的改进,其中一个改进是Jetty所有特性已经体现在Jetty9的文档里。

Jetty有一个口号:不要把应用部署到Jetty上,要把Jetty部署到你的应用里。这句话的意思是把应用打成一个war包部署到Jetty上,不如将Jetty作为应用的一个组件,它可以实例化并像POJO一样。换种说法,在嵌入式模块中运行Jetty意味着把HTTP模块放到你的应用里,这种方法比把你的应用放到一个HTTP服务器里要好。

1.2 概述

本教程中嵌入一个Jetty的典型步骤如下:

  • 创建一个server实例
  • 新增/配置连接
  • 新增/配置处理程序,或者Contexts,或者Servlets
  • 启动Server
  • 等待连接或者在当前线程上做一些其他的事

2. 嵌入式Jetty 使用案例:

2.1 创建一个 server 实例

下面的代码实例化并运行了一个Jetty Server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import org.eclipse.jetty.server.Server;

/**
 * <pre>
 *     一个简单的 jetty 服务
 * </pre>
 *
 * @author saligia
 * @date 17-10-10
 */
public class MyServer {
    public static void main(String [] args) throws Exception {
        Server server = new Server(8080);  // 创建一个 jetty 服务
        server.start();
        server.dumpStdErr();
        server.join();
    }
}

在8080端口运行了一个HTTP服务,因为没有处理程序,所以这不是一个有效的server,所有请求将会返回404错误信息。

2.2 使用处理器处理请求

为了针对请求产生一个相应,Jetty要求用户为服务创建一个处理请求的处理器,一个处理器可以做的工作有:

  • 检测或修改一个请求
  • 一个完整的HTTP响应
  • 处理转发(详见:HandlerWrapper)
  • 处理转发到多个其它处理器上(详见:HandlerCollection)

带有处理器的HelloWorld

接下来的代码HelloHandler.java,表示一个简单的处理程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;

/**
 * <pre>
 * </pre>
 *
 * @author saligia
 * @date 17-10-10
 */
public class HelloWorldHandle extends AbstractHandler {

    /**
     * <pre>
     * </pre>
     *
     * @author
     * @param target              - 目标请求,可以是一个URI或者是一个转发到这的处理器的名字
     * @param request             - Jetty自己的没有被包装的请求,一个可变的Jetty请求对象
     * @param httpServletRequest  - 被filter或者servlet包装的请求,一个不可变的Jetty请求对象
     * @param httpServletResponse - 响应,可能被filter或者servlet包装过
     * @return
     */
    public void handle(String target, Request request, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException, ServletException {
        System.out.println("target  :" + target);
        System.out.println("request : " + request.getRequestURI());
        System.out.println("requestServ : " + httpServletRequest.getRequestURI());


        httpServletResponse.setContentType("text/html; charset=utf-8");
        httpServletResponse.setStatus(HttpServletResponse.SC_OK);

        PrintWriter out = httpServletResponse.getWriter();

        out.println("<h1>Hello world</hello>");
        request.setHandled(true);
    }
}

运行 Hello world 代码 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import org.eclipse.jetty.server.Server;

/**
 * <pre>
 *     一个简单的 jetty 服务
 * </pre>
 *
 * @author saligia
 * @date 17-10-10
 */
public class MyServer {
    public static void main(String [] args) throws Exception {
        Server server = new Server(8080);  // 创建一个 jetty 服务

        server.setHandler(new HelloWorldHandle());

        server.start();
        server.join();
    }
}

一个或多个处理器将处理Jetty所有的请求。一些处理器会转发请求到其他处理器(例如: ContextHandlerCollection 会根据路径选择匹配的ContextHandler),另一些将根据逻辑判断来生成相应的响应(例如:ServletHandler 将把request转发到servlet),还有的处理器将处理和请求无关的操作(例如:RequestLogHandler 或者StatisticsHandler)。

后面的章节将介绍如何在切面调用一个处理器,你可以到org.eclipse.jetty.server.handler 包下看看当前可用的处理器。

2.4 处理器的集合以及封装 :

复杂的请求可以由多个处理器来完成,你可以通过不同的方式把它们结合起来,Jetty有几个 HandlerContainer 接口的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package org.eclipse.jetty.server;

import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.LifeCycle;

@ManagedObject("Handler of Multiple Handlers")
public interface HandlerContainer extends LifeCycle {
    @ManagedAttribute("handlers in this container")
    Handler[] getHandlers();

    @ManagedAttribute("all contained handlers")
    Handler[] getChildHandlers();

    Handler[] getChildHandlersByClass(Class<?> var1);

    <T extends Handler> T getChildHandlerByClass(Class<T> var1);
}

实现类 | 说明 — | — HandlerCollection | 一个包含多个处理器的集合,按照顺序依次处理。这在响应请求的同时进行统计和日志记录特别有用。 HandlerList | 一个包含多个处理器的集合,按照顺序处理,与HandlerCollection不同的是,当处理器出现异常或者响应被提交或者 request.isHandled() 方法返回true时,后续将不再被调用。一般用来匹配不同的主机,用来进行不同的处理。 HandlerWrapper | 一个处理器的基类用来进行切面编程。例如,一个标准的web应用会由context,session,安全和servlet处理器构成。 ContextHandlerCollection | 一个特殊的HandlerCollection,使用完整的URI前缀来选择匹配的ContextHandler对请求进行处理。

2.5 处理器的作用域 :

在Jetty中,很多标准的服务器会继承HandlerWrappers,用来进行链式处理,比如从请求从 ContextHandlerSessionHandler ,再到 SecurityHandler 最后到ServletHandler。然而,因为servlet规范的性质,外部处理器不能在没有调用内部处理器的时候得知内部处理器的信息,例如:ContextHandler调用应用监听请求的context时,它必须已经知道ServletHandler将要转发请求到哪个servlet,以确保servletPath方法返回正确的值。

ScopedHandler是HandlerWrapper 一个抽象实现类,用来提供链式调用时作用域的支持。例如:一个ServletHandler内嵌在一个ContextHandler中,方法嵌套调用的顺序为:

1
2
3
4
5
6
Server.handle(...)
  ContextHandler.doScope(...)
    ServletHandler.doScope(...)
      ContextHandler.doHandle(...)
        ServletHandler.doHandle(...)
          SomeServlet.service(...)

因此ContextHandler处理请求时,它内嵌的ServletHandler已经建立了。

2.6 嵌入 ResourceHandle

你可以使用 ResourceHandler 来处理当前工作路径下的静态资源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.server.handler.gzip.GzipHandler;

import java.io.File;

/**
 * <pre>
 * </pre>
 *
 * @author saligia
 * @date 17-10-11
 */
public class MyResource {
    public static void main(String [] args) throws Exception {
        Server server = new Server(8080);

        //创建一个ResourceHandler,它处理请求的方式是提供一个资源文件
        //这是一个Jetty内置的处理器,所以它非常适合与其他处理器构成一个处理链
        ResourceHandler resourceHandler = new ResourceHandler();

        //配置ResourceHandler,设置哪个文件应该被提供给请求方
        //这个例子里,配置的是当前路径下的文件,但是实际上可以配置长任何jvm能访问到的地方
        resourceHandler.setDirectoriesListed(true);
        resourceHandler.setWelcomeFiles(new String[] { "index.html" });
        resourceHandler.setResourceBase("src/main/web");

        // 将resource_handler添加到GzipHandler中,然后将GzipHandler提供给Server
        GzipHandler gzip = new GzipHandler();
        server.setHandler(gzip);

        HandlerList handlers = new HandlerList();
        handlers.setHandlers(new Handler[] { resourceHandler, new DefaultHandler()});
        server.setHandler(handlers);

        server.start();
        server.join();
    }
}

在这个例子中,连接将处理http的请求,这个也是默认的ServerConnector连接类。

2.7 多个连接的例子

当配置多个连接(例如:HTTP和HTTPS),它们可能是共同分享HTTP设置的参数。为了显式的配置 ServerConnector 需要使用 ConnectionFactory ,并提供一个常用的HTTP配置。

下面这个 ManyConnectors例子,给一个Server配置了两个ServerConnector ,http连接有一个HTTPConnectionFactory 实例,https连接有一个SslConnectionFactory 实例在HttpConnectionFactory里面。两个HttpConnectionFactory 都是基于同一个HttpConfiguration实例,然而https使用包装过的配置信息,因此SecureRequestCustomizer 可以被添加进去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import java.io.File;
import java.io.FileNotFoundException;

import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.util.ssl.SslContextFactory;

/**
 * 一个有多个连接的Jetty例子
 */
public class ManyConnectors {
    public static void main(String[] args) throws Exception {

        //这个例子会展示如何配置SSL,我们需要一个秘钥库,会在jetty.home下面找
        String jettyDistKeystore = "../../jetty-distribution/target/distribution/demo-base/etc/keystore";
        String keystorePath = System.getProperty("example.keystore", jettyDistKeystore);
        File keystoreFile = new File(keystorePath);
        if (!keystoreFile.exists()) {
            throw new FileNotFoundException(keystoreFile.getAbsolutePath());
        }

        //创建一个不指定端口的Server,随后将直接配置连接和端口
        Server server = new Server();

        //HTTP配置
        //HttpConfiguration是一个配置http和https属性的集合,默认的配置是http的
        //带secured的ui配置https的,
        HttpConfiguration http_config = new HttpConfiguration();
        http_config.setSecureScheme("https");
        http_config.setSecurePort(8443);
        http_config.setOutputBufferSize(32768);

        //HTTP连接
        //第一个创建的连接是http连接,传入刚才创建的配置信息,也可以重新设置新的配置,如端口,超时等
        ServerConnector http = new ServerConnector(server, new HttpConnectionFactory(http_config));
        http.setPort(8080);
        http.setIdleTimeout(30000);

        //使用SslContextFactory来创建http
        //SSL需要一个证书,所以我们配置一个工厂来获得需要的东西
        SslContextFactory sslContextFactory = new SslContextFactory();
        sslContextFactory.setKeyStorePath(keystoreFile.getAbsolutePath());
        sslContextFactory.setKeyStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4");
        sslContextFactory.setKeyManagerPassword("OBF:1u2u1wml1z7s1z7a1wnl1u2g");

        //HTTPS的配置类
        HttpConfiguration https_config = new HttpConfiguration(http_config);
        SecureRequestCustomizer src = new SecureRequestCustomizer();
        src.setStsMaxAge(2000);
        src.setStsIncludeSubDomains(true);
        https_config.addCustomizer(src);

        //HTTPS连接
        //创建第二个连接,
        ServerConnector https = new ServerConnector(server,
                new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()),
                new HttpConnectionFactory(https_config));
        https.setPort(8443);
        https.setIdleTimeout(500000);

        // 设置一个连接的集合
        server.setConnectors(new Connector[] { http, https });

        // 设置一个处理器
        server.setHandler(new HelloHandler());

        // 启动服务
        server.start();
        server.join();
    }
}

2.8 ServletHandle

Servlets 是处理逻辑和HTTP请求的标准方式。Servlets 类似于Jetty的处理器,request对象不可变且不能被修改。在Jetty中servlet将有ServletHandler进行负责调用。它使用标准的路径匹配一个请求到servlet,设置请求的路径和请求内容,将请求传递到servlet,或者通过过滤器产生一个响应。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;

public class MinimalServlets {
    public static void main(String[] args) throws Exception {

        Server server = new Server(8080);
        //ServletHandler通过一个servlet创建了一个非常简单的context处理器
        //这个处理器需要在Server上注册
        ServletHandler handler = new ServletHandler();
        server.setHandler(handler);

        //传入能匹配到这个servlet的路径
        //提示:这是一个未经处理的servlet,没有通过web.xml或@WebServlet注解或其他方式配置
        handler.addServletWithMapping(HelloServlet.class, "/*");

        server.start();
        server.join();
    }

    @SuppressWarnings("serial")
    public static class HelloServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            response.setContentType("text/html");
            response.setStatus(HttpServletResponse.SC_OK);
            response.getWriter().println("<h1>Hello from HelloServlet</h1>");
        }
    }
}

2.9 嵌入 Context(ContextHandler)

  • ContextHandler是一种ScopedHandler,只用来响应配匹配指定URI前缀的请求,
  • 一个Classloader 当在一个请求作用域里的时候处理当前线程的请求
  • 一个ServletContext有小属性的集合
  • 通过ServletContext获得的初始化参数的集合
  • 通过ServletContext 获得的基础资源的集合
  • 一个虚拟主机名称的集合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ResourceHandler;

/**
 * <pre>
 *     一个简单的 jetty 服务
 * </pre>
 *
 * @author saligia
 * @date 17-10-10
 */
public class MyServer {
    public static void main(String [] args) throws Exception {
        Server server = new Server(8080);  // 创建一个 jetty 服务

        ContextHandler contextHandler = new ContextHandler();

        ResourceHandler resourceHandler = new ResourceHandler();

        //配置ResourceHandler,设置哪个文件应该被提供给请求方
        //这个例子里,配置的是当前路径下的文件,但是实际上可以配置长任何jvm能访问到的地方
        resourceHandler.setDirectoriesListed(true);
        resourceHandler.setWelcomeFiles(new String[] { "index.html" });
        resourceHandler.setResourceBase("src/main/web");

        contextHandler.setContextPath("/test");
        contextHandler.setHandler(resourceHandler);

        server.setHandler(contextHandler);

        server.start();
        server.join();
    }
}

2.10 嵌入 ServletContextHandler

ServletContextHandler是一种特殊的ContextHandler,它可以支持标准的sessions 和Servlets。下面例子的OneServletContext 实例化了一个 DefaultServlet为/tmp/ 和DumpServlet 提供静态资源服务,DumpServlet 创建session并且应答请求信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ServletContextHandler;

public class OneServletContext {
    public static void main(String[] args) throws Exception {
        Server server = new Server(8080);

        ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
        context.setContextPath("/");
        context.setResourceBase(System.getProperty("java.io.tmpdir"));
        server.setHandler(context);

        // 增加一个 dump servlet
        context.addServlet(DumpServlet.class, "/dump/*");
        // 增加一个默认的servlet
        context.addServlet(DefaultServlet.class, "/");

        server.start();
        server.join();
    }
}

当有很多有效的contexts 时,可以创建一个ContextHandlerCollection 集合存储这些处理器,下面的这个ManyContexts例子展示多个context的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;

public class ManyContexts
{
    public static void main( String[] args ) throws Exception
    {
        Server server = new Server(8080);

        ContextHandler context = new ContextHandler("/");
        context.setContextPath("/");
        context.setHandler(new HelloHandler("Root Hello"));

        ContextHandler contextFR = new ContextHandler("/fr");
        contextFR.setHandler(new HelloHandler("Bonjoir"));

        ContextHandler contextIT = new ContextHandler("/it");
        contextIT.setHandler(new HelloHandler("Bongiorno"));

        ContextHandler contextV = new ContextHandler("/");
        contextV.setVirtualHosts(new String[] { "127.0.0.2" });
        contextV.setHandler(new HelloHandler("Virtual Hello"));

        ContextHandlerCollection contexts = new ContextHandlerCollection();
        contexts.setHandlers(new Handler[] { context, contextFR, contextIT,
                contextV });

        server.setHandler(contexts);

        server.start();
        server.join();
    }
}

2.11 嵌入 web 应用(WebAppContext):

WebAppContext是ServletContextHandler 的扩展,使用标准的web应用组件和web.xml,通过web.xml和注解配置servlet,filter和其它特性。下面这个OneWebApp例子部署了一个简单的web应用。Web应用程序可以使用容器提供的资源,在这种情况下需要一个LoginService并配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.io.File;
import java.lang.management.ManagementFactory;

import org.eclipse.jetty.jmx.MBeanContainer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AllowSymLinkAliasChecker;
import org.eclipse.jetty.webapp.WebAppContext;

public class OneWebApp {
    public static void main(String[] args) throws Exception {
        Server server = new Server(8080);

        // 设置 JMX
        MBeanContainer mbContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
        server.addBean(mbContainer);

        //下面这个web应用是一个完整的web应用,在这个例子里设置/为根路径,web应用所有的配置都是有效的,
        WebAppContext webapp = new WebAppContext();
        webapp.setContextPath("/");
        File warFile = new File("../../jetty-distribution/target/distribution/test/webapps/test/");
        webapp.setWar(warFile.getAbsolutePath());
        webapp.addAliasCheck(new AllowSymLinkAliasChecker());

        //将web应用设置到server里
        server.setHandler(webapp);

        server.start();
        server.join();
    }
}

servlet 3 的新特性通过继承 ServletContainerInitializer 类来实现web.xml,如果用户有实现这个接口,可以通过这种配置方式启动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Main {
    public static void main(String[] args) throws Exception {
        Server server = new Server();

        // 链接管理
        ServerConnector serverConnector = new ServerConnector(server);
        serverConnector.setPort(8080);
        serverConnector.setReuseAddress(true);
        server.setConnectors(new Connector[]{serverConnector});


        WebAppContext context = new WebAppContext();

        context.setContextPath("/");
        context.setParentLoaderPriority(true);
        context.setResourceBase("lib");
        context.setConfigurations(new Configuration[] {
                new AnnotationConfiguration(),
                new WebInfConfiguration(), new EnvConfiguration() });
        context.setAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern", ".*.jar");

        server.setHandler(context);

        server.start();
        server.dumpStdErr();
        server.join();
    }
}
本文由作者按照 CC BY 4.0 进行授权

© . 保留部分权利。

本站采用 Jekyll 主题 Chirpy