嵌入式 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教程。