嵌入式 Jetty
最后修改于 2024 年 1 月 27 日
Jetty 可以以嵌入式模式运行。这意味着不需要构建 WAR 文件并将其部署到独立的 Jetty 服务器。Jetty 是一个软件组件,可以像任何其他 POJO(Plain Old Java Object)一样被实例化和使用。
简单处理器
Handler 是一个处理传入请求的组件。请求在 handle 方法中处理。Handlers 会接收 servlet API 的请求和响应对象,但它们本身不是 servlets。
$ mkdir simplehandler $ cd simplehandler/ $ mkdir -p src/com/zetcode $ mkdir build $ touch src/com/zetcode/SimpleHandlerEx.java
我们创建一个新的项目目录及其结构。
SimpleHandlerEx.java
package com.zetcode;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;
public class SimpleHandlerEx extends AbstractHandler {
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request,
HttpServletResponse response) throws IOException, ServletException {
response.setContentType("text/plain;charset=utf-8");
response.setStatus(HttpServletResponse.SC_OK);
baseRequest.setHandled(true);
response.getWriter().println("Hello there");
}
public static void main(String[] args) throws Exception {
Server server = new Server(8080);
server.setHandler(new SimpleHandlerEx());
server.start();
server.join();
}
}
服务器启动,并为传入请求设置了处理器。
response.setContentType("text/plain;charset=utf-8");
由于我们只输出纯文本,因此我们将媒体类型设置为 text/plain。Internet 媒体类型是 Internet 上用于指示文件包含的数据类型的标准标识符。
response.getWriter().println("Hello there");
作为响应,我们发送了一个简单的消息。getWriter 方法返回一个 PrintWriter 对象,该对象将字符文本发送到客户端。
server.join();
join 方法使服务器线程与当前线程合并。它是阻塞的,直到服务器准备就绪。该方法还会等待直到服务器完全停止。
<?xml version="1.0" encoding="UTF-8"?>
<project name="SimpleHandlerEx" default="compile">
<property name="name" value="simplehandler"/>
<property environment="env"/>
<property name="src.dir" value="src"/>
<property name="build.dir" value="build"/>
<property name="jetty.lib.dir" location="${env.JETTY_HOME}/lib"/>
<path id="compile.classpath">
<fileset dir="${jetty.lib.dir}">
<include name="**/*.jar"/>
</fileset>
</path>
<path id="run.classpath">
<pathelement path="${build.dir}"/>
<fileset dir="${jetty.lib.dir}">
<include name="**/*.jar"/>
</fileset>
</path>
<target name="init">
<mkdir dir="${build.dir}"/>
</target>
<target name="compile" depends="init">
<javac srcdir="${src.dir}" destdir="${build.dir}"
includeantruntime="false">
<classpath refid="compile.classpath"/>
</javac>
<echo>Compilation completed</echo>
</target>
<target name="run" depends="compile">
<echo>Running the program</echo>
<java classname="com.zetcode.SimpleHandlerEx"
classpathref="run.classpath"/>
</target>
<target name="clean" depends="init">
<delete dir="${build.dir}"/>
<echo>Cleaning completed</echo>
</target>
</project>
构建文件包含一个 run 任务,该任务执行编译后的应用程序。
<property name="jetty.lib.dir" location="${env.JETTY_HOME}/lib"/>
为了编译和运行我们的示例,我们需要一些 JAR 文件。它们位于 Jetty 主目录的 lib/ 子目录中。
$ ant run $ curl localhost:8080 Hello there
当我们使用 curl 应用程序发送一个简单的 GET 请求时,我们会收到此消息。
定义上下文根
Web 应用程序的上下文根决定了哪些 URL 将被委派给该应用程序。Jetty 有 ContextHandler 来定义应用程序的上下文。
ContextEx.java
package com.zetcode;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.ContextHandler;
class MyHandler extends AbstractHandler {
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request,
HttpServletResponse response) throws IOException, ServletException {
response.setContentType("text/plain;charset=utf-8");
response.setStatus(HttpServletResponse.SC_OK);
baseRequest.setHandled(true);
response.getWriter().println("Hello there");
}
}
public class ContextEx {
public static void main(String[] args) throws Exception {
Server server = new Server(8080);
ContextHandler con = new ContextHandler();
con.setContextPath("/path");
con.setHandler(new MyHandler());
server.setHandler(con);
server.start();
server.join();
}
}
我们的应用程序的处理器将为名为 /path 的上下文根调用。
ContextHandler context = new ContextHandler();
context.setContextPath("/path");
实例化一个 ContextHandler 并设置路径。
context.setHandler(new MyHandler());
将处理器设置到上下文处理对象上。
server.setHandler(context);
我们使用 setHandler 方法将上下文处理对象设置到服务器上。
$ curl localhost:8080/path/ Hello there
我们将请求发送到定义的上下文根。对于不匹配的上下文,Jetty 返回 404 错误消息。
简单的 Servlet
在下面的示例中,当向嵌入式 Jetty 发送带有特定 URL 的请求时,将启动一个 Java servlet。
$ mkdir simpleservlet $ cd simpleservlet/ $ mkdir -p src/com/zetcode/ $ touch src/com/zetcode/SimpleServlet.java $ touch src/com/zetcode/SimpleApp.java $ touch src/web/WEB-INF/web.xml $ touch build.xml
我们创建了一个项目目录及其结构。我们使用 Ant 来构建项目,并使用 web.xml 部署描述符来配置我们的 Web 应用程序。
$ tree
.
├── build.xml
└── src
├── com
│ └── zetcode
│ ├── SimpleApp.java
│ └── SimpleServlet.java
└── web
└── WEB-INF
└── web.xml
5 directories, 4 files
此时,这是项目目录的内容。
package com.zetcode;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.WebAppContext;
public class SimpleApp {
public static void main(String[] args) throws Exception {
String webdir = "src/web/";
Server server = new Server(8080);
WebAppContext wcon = new WebAppContext();
wcon.setContextPath("/simserv");
wcon.setDescriptor(webdir + "/WEB-INF/web.xml");
wcon.setResourceBase(webdir);
wcon.setParentLoaderPriority(true);
server.setHandler(wcon);
server.start();
server.join();
}
}
为了在嵌入式模式下配置我们的 Web 应用程序,我们使用了 WebAppContext 类。此类是 ContextHandler 的扩展,它协调 Web 应用程序的处理器的构建和配置。
wcon.setContextPath("/simserv");
使用 setContextPath 方法定义上下文路径。
wcon.setDescriptor(webdir + "/WEB-INF/web.xml");
使用 setDescriptor 方法设置部署描述符。
wcon.setResourceBase(webdir);
我们为应用程序设置文档根。它是包含上下文的静态资源的目录(或目录集合或 URL)。这些可以是图像、HTML 文件或 JSP 文件。
package com.zetcode;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.*;
public class SimpleServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/plain");
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().println("Simple servlet");
}
}
SimpleServlet 将以纯文本消息进行响应。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<servlet>
<servlet-name>SimpleServlet</servlet-name>
<servlet-class>com.zetcode.SimpleServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>SimpleServlet</servlet-name>
<url-pattern>simple.do</url-pattern>
</servlet-mapping>
</web-app>
在 web.xml 文件中,com.zetcode.SimpleServlet 的执行被映射到字符串 simple.do。这必须在请求 URL 的末尾指定。
<?xml version="1.0" encoding="UTF-8"?>
<project name="SimpleServlet" default="compile">
<property name="name" value="simple"/>
<property environment="env"/>
<property name="src.dir" value="src"/>
<property name="web.dir" value="${src.dir}/web"/>
<property name="build.dir" location="${web.dir}/WEB-INF/classes"/>
<property name="jetty.lib.dir" location="${env.JETTY_HOME}/lib"/>
<path id="compile.classpath">
<fileset dir="${jetty.lib.dir}">
<include name="**/*.jar"/>
</fileset>
</path>
<path id="run.classpath">
<pathelement path="${build.dir}" />
<fileset dir="${jetty.lib.dir}">
<include name="**/*.jar"/>
</fileset>
</path>
<target name="init">
<mkdir dir="${build.dir}"/>
</target>
<target name="compile" depends="init">
<javac srcdir="${src.dir}" destdir="${build.dir}"
includeantruntime="false">
<classpath refid="compile.classpath"/>
</javac>
<echo>Compilation completed</echo>
</target>
<target name="clean" depends="init">
<delete dir="${build.dir}"/>
<echo>Cleaning completed</echo>
</target>
<target name="run" depends="compile">
<echo>Running the program</echo>
<java classname="com.zetcode.SimpleApp"
classpathref="run.classpath"/>
</target>
</project>
这是配套的 Ant 构建文件。
$ ant run $ curl localhost:8080/simserv/simple.do Simple servlet
我们构建并运行应用程序。向 localhost:8080/simserv/simple.do 发送 GET 请求会返回“Simple servlet”纯文本消息。
@WebServlet 注解
@WebServlet 注解用于声明一个 servlet。该注解由 servlet 容器在部署时处理。声明的 servlet 可在指定的 URL 模式下访问。
$ tree
.
├── build.xml
└── src
├── com
│ └── zetcode
│ ├── AnnotatedAppEx.java
│ └── MyServlet.java
└── web
└── WEB-INF
5 directories, 3 files
这是我们项目目录的内容。在此示例中,我们不包含 web.xml 文件,因为使用 Servlet 3.1 API,此文件的包含是可选的。
package com.zetcode;
import org.eclipse.jetty.annotations.AnnotationConfiguration;
import org.eclipse.jetty.plus.webapp.EnvConfiguration;
import org.eclipse.jetty.plus.webapp.PlusConfiguration;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.FragmentConfiguration;
import org.eclipse.jetty.webapp.MetaInfConfiguration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.webapp.WebInfConfiguration;
import org.eclipse.jetty.webapp.WebXmlConfiguration;
public class AnnotatedAppEx {
public static void main(String[] args) throws Exception {
String webdir = "src/web/";
Server server = new Server(8080);
WebAppContext wcon = new WebAppContext();
wcon.setResourceBase(webdir);
wcon.setContextPath("/annotated");
wcon.setConfigurations(new Configuration[] {
new AnnotationConfiguration(), new WebXmlConfiguration(),
new WebInfConfiguration(), new PlusConfiguration(),
new MetaInfConfiguration(), new FragmentConfiguration(),
new EnvConfiguration() });
wcon.setParentLoaderPriority(true);
server.setHandler(wcon);
server.start();
server.join();
}
}
此类启动了一个启用了注解的嵌入式 Jetty 服务器。
wcon.setConfigurations(new Configuration[] {
new AnnotationConfiguration(), new WebXmlConfiguration(),
new WebInfConfiguration(), new PlusConfiguration(),
new MetaInfConfiguration(), new FragmentConfiguration(),
new EnvConfiguration() });
为了启用注解,我们需要设置这些配置类。这提供了对多个功能的支持。
package com.zetcode;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(urlPatterns = { "/aserv" })
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/plain");
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().println("MyServlet called");
}
}
在 MyServlet.java 文件中,我们提供了 @WebServlet 注解。该 servlet 可在 /aserv URL 下访问。
<?xml version="1.0" encoding="UTF-8"?>
<project name="AnnotatedServlet" default="compile">
<property name="name" value="annotated"/>
<property environment="env"/>
<property name="src.dir" value="src"/>
<property name="web.dir" value="${src.dir}/web"/>
<property name="build.dir" location="${web.dir}/WEB-INF/classes"/>
<property name="jetty.lib.dir" location="${env.JETTY_HOME}/lib"/>
<path id="compile.classpath">
<fileset dir="${jetty.lib.dir}">
<include name="**/*.jar"/>
</fileset>
</path>
<path id="run.classpath">
<pathelement path="${build.dir}"/>
<fileset dir="${jetty.lib.dir}">
<include name="**/*.jar"/>
</fileset>
</path>
<target name="init">
<mkdir dir="${build.dir}"/>
</target>
<target name="compile" depends="init">
<javac srcdir="${src.dir}" destdir="${build.dir}"
includeantruntime="false">
<classpath refid="compile.classpath"/>
</javac>
<echo>Compilation completed</echo>
</target>
<target name="clean" depends="init">
<delete dir="${build.dir}"/>
<echo>Cleaning completed</echo>
</target>
<target name="run" depends="compile">
<echo>Running the program</echo>
<java classname="com.zetcode.AnnotatedAppEx"
classpathref="run.classpath"/>
</target>
</project>
这是我们项目的 Ant 构建文件。
$ ant run $ curl localhost:8080/annotated/aserv MyServlet called
MyServlet 已成功启动。
启用 JavaServer Pages
为了在嵌入式 Jetty 模式下启用 JavaServer Pages,我们需要创建一个临时 servlet 目录,设置一个非系统类加载器,添加一个 jsp servlet,以及添加一个 default servlet。
在下面的示例中,我们创建一个简单的 JavaServer Page 并构建一个 WAR 存档。此存档将被设置到以嵌入式模式启动的 Jetty。
$ pwd
/home/janbodnar/prog/jetty/jspexample
$ tree
.
├── build.xml
└── src
├── com
│ └── zetcode
│ └── JSPExample.java
└── web
├── index.jsp
└── WEB-INF
5 directories, 3 files
这是项目结构。
<!DOCTYPE html> <html> <body> <p> Today's date: <%= (new java.util.Date()).toLocaleString() %> </p> </body> </html>
我们的 JSP 将输出当前本地化的日期。
package com.zetcode;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import org.apache.jasper.servlet.JspServlet;
import org.apache.tomcat.InstanceManager;
import org.apache.tomcat.SimpleInstanceManager;
import org.eclipse.jetty.annotations.ServletContainerInitializersStarter;
import org.eclipse.jetty.apache.jsp.JettyJasperInitializer;
import org.eclipse.jetty.plus.annotation.ContainerInitializer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.webapp.WebAppContext;
public class JSPExample {
private void startServer() throws Exception {
String jetty_base = "/home/janbodnar/prog/jetty/my-base";
File tmpdir = new File(System.getProperty("java.io.tmpdir"));
File scdir = new File(tmpdir.toString(), "embedded-jetty-jsp");
if (!scdir.exists()) {
if (!scdir.mkdirs()) {
throw new IOException("Unable to create scratch directory: " + scdir);
}
}
Server server = new Server(8080);
WebAppContext wcon = new WebAppContext();
wcon.setParentLoaderPriority(true);
wcon.setContextPath("/");
wcon.setAttribute("javax.servlet.wcon.tempdir", scdir);
wcon.setAttribute(InstanceManager.class.getName(),
new SimpleInstanceManager());
server.setHandler(wcon);
JettyJasperInitializer sci = new JettyJasperInitializer();
ServletContainerInitializersStarter sciStarter =
new ServletContainerInitializersStarter(wcon);
ContainerInitializer initializer = new ContainerInitializer(sci, null);
List<ContainerInitializer> initializers = new ArrayList<>();
initializers.add(initializer);
wcon.setAttribute("org.eclipse.jetty.containerInitializers", initializers);
wcon.addBean(sciStarter, true);
ClassLoader jspClassLoader = new URLClassLoader(new URL[0],
this.getClass().getClassLoader());
wcon.setClassLoader(jspClassLoader);
ServletHolder holderJsp = new ServletHolder("jsp", JspServlet.class);
holderJsp.setInitOrder(0);
holderJsp.setInitParameter("fork","false");
holderJsp.setInitParameter("keepgenerated", "true");
wcon.addServlet(holderJsp, "*.jsp");
ServletHolder holderDefault = new ServletHolder("default",
DefaultServlet.class);
holderDefault.setInitParameter("dirAllowed", "true");
wcon.addServlet(holderDefault, "/");
wcon.setWar(jetty_base + "/webapps/jspexample.war");
server.setHandler(wcon);
server.start();
server.join();
}
public static void main(String[] args) throws Exception {
JSPExample ex = new JSPExample();
ex.startServer();
}
}
我们启用 JSP 支持,并将一个名为 jspexample.war 的 WAR 文件设置到 Web 应用程序上下文中。
File tmpdir = new File(System.getProperty("java.io.tmpdir"));
File scdir = new File(tmpdir.toString(), "embedded-jetty-jsp");
if (!scdir.exists()) {
if (!scdir.mkdirs()) {
throw new IOException("Unable to create scratch directory: " + scdir);
}
}
我们为 servlet 上下文创建了一个临时目录。它在 JSP 编译期间使用。
System.setProperty("org.apache.jasper.compiler.disablejsr199","false");
这一行强制使用标准 javac。(也有 Eclipse 使用的。)
JettyJasperInitializer sci = new JettyJasperInitializer();
ServletContainerInitializersStarter sciStarter =
new ServletContainerInitializersStarter(wcon);
ContainerInitializer initializer = new ContainerInitializer(sci, null);
List<ContainerInitializer> initializers = new ArrayList<>();
initializers.add(initializer);
wcon.setAttribute("org.eclipse.jetty.containerInitializers", initializers);
wcon.addBean(sciStarter, true);
这是 JSP 初始化代码。
ClassLoader jspClassLoader = new URLClassLoader(new URL[0],
this.getClass().getClassLoader());
wcon.setClassLoader(jspClassLoader);
JSP 需要一个非系统类加载器,这只是包装了嵌入式系统类加载器,使其适合 JSP 使用。
ServletHolder holderJsp = new ServletHolder("jsp", JspServlet.class);
holderJsp.setInitOrder(0);
holderJsp.setInitParameter("fork","false");
holderJsp.setInitParameter("keepgenerated", "true");
wcon.addServlet(holderJsp, "*.jsp");
我们添加了一个 JspServlet。它的名称必须是“jsp”。
ServletHolder holderDefault = new ServletHolder("default",
DefaultServlet.class);
holderDefault.setInitParameter("dirAllowed", "true");
wcon.addServlet(holderDefault, "/");
我们添加了一个 DefaultServlet。它的名称必须是“default”。
wcon.setWar(jetty_base + "/webapps/jspexample.war");
WAR 文件被设置到 Web 应用程序上下文中。
<?xml version="1.0" encoding="UTF-8"?>
<project name="JSPExample" default="compile">
<property name="name" value="jspexample"/>
<property environment="env"/>
<property name="src.dir" value="src"/>
<property name="web.dir" value="${src.dir}/web"/>
<property name="build.dir" location="${web.dir}/WEB-INF/classes"/>
<property name="dist.dir" location="dist"/>
<property name="jetty.lib.dir" location="${env.JETTY_HOME}/lib"/>
<property name="jetty.base" location="${env.JETTY_BASE}"/>
<property name="deploy.path" location="${jetty.base}/webapps"/>
<path id="compile.classpath">
<fileset dir="${jetty.lib.dir}">
<include name="**/*.jar"/>
</fileset>
</path>
<path id="run.classpath">
<pathelement path="${build.dir}"/>
<fileset dir="${jetty.lib.dir}">
<include name="**/*.jar"/>
</fileset>
</path>
<target name="init">
<mkdir dir="${build.dir}"/>
<mkdir dir="${dist.dir}"/>
</target>
<target name="compile" depends="init">
<javac srcdir="${src.dir}" destdir="${build.dir}"
includeantruntime="false">
<classpath refid="compile.classpath"/>
</javac>
<echo>Compilation completed</echo>
</target>
<target name="archive" depends="compile">
<war destfile="${dist.dir}/${name}.war" needxmlfile="false">
<fileset dir="${web.dir}"/>
</war>
<echo>Archive created</echo>
</target>
<target name="clean" depends="init">
<delete dir="${build.dir}"/>
<delete dir="${dist.dir}"/>
<echo>Cleaning completed</echo>
</target>
<target name="deploy" depends="archive">
<copy file="${dist.dir}/${name}.war" todir="${deploy.path}"/>
<echo>Archive deployed</echo>
</target>
<target name="run" depends="deploy">
<echo>Running the program</echo>
<java classname="com.zetcode.JSPExample"
classpathref="run.classpath"/>
</target>
</project>
这是我们项目的 Ant 构建。所有必需的 jar 文件都在 JETTY_HOME 目录的 lib 子目录中。
执行 ant run 会导致以下安全异常:java.security.AccessControlException: access denied ("java.lang.RuntimePermission" "getClassLoader")。
我们需要向 java.policy 文件添加一个配置选项。
$ vi $JAVA_HOME/jre/lib/security/java.policy
我们编辑 java.policy 文件。
permission java.lang.RuntimePermission "getClassLoader", "read";
我们添加此权限。
$ ant run
$ curl localhost:8080/index.jsp
<!DOCTYPE html>
<html>
<body>
<p>
Today's date: Sep 16, 2014 1:54:05 PM
</p>
</body>
</html>
JSP 返回当前日期。
在 Jetty 教程的这一部分,我们处理了嵌入式模式下的 Jetty。我们定义了一个简单的处理器和 servlet,使用了 @WebServlet 注解,并启用了 JSP 支持。
作者
列出所有Java教程。