延续是一种机制用来实现相似于Servlet 3.0异步功能的异步Servlet,但提供了一个简单易操做的接口。php
不使用异步IO:css
异步servlet的概念每每与异步IO或NIO的使用产生混淆。可是异步Servlets 和异步IO仍是有主要不一样点:html
异步等待:java
异步servlets 的主要用法是用来等待非IO的事件或资源。许多web应用程序须要等待处理HTTP请求的各类阶段,例如:web
servlet API(2.5以前)仅支持一种同步调用方式,因此servlet 的任何等待都是阻塞式的。不幸的是,这意味着分配给请求的线程将会在等待全部资源的时候被持有:内核线程、栈存储、缓冲池、字符转换器、EE认真context等等。保存对这些资源的等待会浪费大量的系统资源。若是等待是异步进行的,那么能够进行更好的扩展和提升服务质量。redis
AJAX服务端推送spring
Web 2.0可使用comet技术(又叫作 AJAX推送、服务端推送、长轮询)动态更新一个页面,而不须要刷新整个页面。数据库
考虑一个股票投资的web应用程序。每一个浏览器都会发一个长轮询到服务器,要求服务器提供任何用户股票价格的变更。服务器将从它全部的客户端接收到长轮询请求,而且不会当即进行响应。服务器将等待股票价格的变更,在那时它将发送一个响应到全部等待着的客户端。客户端接收到长轮询响应后会当即再次发送一个长轮询来得到将来价格的改变。apache
这样的话,服务器将持有每个用户链接的长轮询,因此若是servlet不是异步的话,那么至少须要1000个线程来持有同时1000各用户。1000个线程会消耗256M资源;这些资源最好被应用所使用而不是进行无心义的等待。编程
若是servlet是异步的话,那么须要的线程数量将由生成相应的时间和价格变化的频率所决定。若是每一个用户每10秒接收一次请求,响应须要10ms来生成,那么1000个用户仅仅须要一个线程来提供服务,256M的栈资源将被释放用于其余用途。
想得到更多关于comet 的知识,能够阅读comet 项目的与Jetty异步工做章节。
异步RESTful Web Service
假设一个web应用访问一个远程web服务(例如,SOAP service或RESTful service),一般一个远程服务仅花几百毫秒即可产生一个响应,eBay的RESTful web服务一般须要350ms来匹配给定关键字的拍卖列表-虽然仅仅只有少数的10ms的CPU时间来处理本地请求和生成一个响应。
为了每秒处理1000个请求,每个web服务调用须要200ms,那么一个web应用须要1000*(200+20)/1000 = 220 个线程和110MB内存。若是发生请求风暴时仍会致使线程不足和web服务变慢的状况。若是进行异步处理,那么web应用将不须要持有每个线程在等待web service响应的时候。及时异步机制须要消耗10ms(一般不消耗),那么web应用将须要1000*(20+10)/1000 = 30 个线程和15MB内存。这将有86%的性能提高,和95MB的内存释放可用于其余地方。并且,若是多重web services请求须要,异步调用将容许并行调用,而不是顺序调用,不须要额外分配线程。
这有一个Jetty的异步解决方案,查看例子。
服务质量(例如,JDBC链接池)
假设一个web应用每秒处理400个应用请求,每一个请求须要与数据库交互50ms。为了处理这些请求,平均每秒须要400*50/1000 = 20 个JDBC链接。然而请求常常爆发或者延迟。为了保护访问风暴下的数据库,一般使用数据库链接池来限制链接。因此对于这个应用程序,它将是合理应用JDBC 30链接池,提供50%的额外保证。
若是请求在某一时刻翻倍,那么30个链接将不能每秒处理600个请求,那么每秒200个请求将要等待链接池分配链接。若是一个servlet容器有一个200个线程的线程池,那么全部的线程将要等待链接池1秒钟。1秒事后,web应用将不能处理任何请求,由于全部的线程都不可用,即使请求不是用数据库。加倍线程池数量须要额外的100MB内存,而只会给应用程序另外1mc的负载恩典。
这个线程饥饿情况也会发生若是数据库运行缓慢或暂时不可用。线程饥饿是频发的问题,并致使整个web服务来锁定和变得反应迟钝。若是web容器可以不使用线程来暂停等待一个JDBC链接请求,那么线程饥饿就不会发生,由于只有30个线程被用来访问数据库,而其余470个线程用于处理请求,不访问数据库。
Jetty的解决方案的一个示例,请参阅Server过滤器的质量。
Java servlet的可伸缩性能的主要缘由是由服务线程模型引发:
每链接一个线程
传统的Java IO模型,每一个TCP/IP 链接与一个线程关联。若是你有一些很是活跃的线程,那么这个模型能够扩展到很是高的每秒请求数。
然而,许多web应用程序的典型特性是许多持久的HTTP链接等待用户阅读完网页,或者搜索下一个点击的链接。这样的配置,thread-per-connection模型将会有成千的线程须要支持成千的用户的大规模部署问题。
每请求一个线程
Java NIO库支持异步IO,这样线程就不须要分配到每一个链接上,当链接闲置时(两次请求中间),那么链接将会添加到NIO选择集合,它容许一个线程扫描许多活动链接。只有当IO被检测到输入输出的时候线程才会被分配给它。然而servlet 2.5 API模型仍然须要将一个线程分配给一个请求持用的全部时间内。
这种thread-per-request模型容许更大比例的链接(用户)因为更少的调度延时致使的每秒请求数的减小。
异步请求处理
Jetty的支持链接API(和 servlet 3.0 异步)在servlet API引入一种改变,就是容许将一个请求屡次分配到一个servlet。若是这个servlet没有所需的资源,那么这个servlet将被挂起(或将它放入异步模型),那么这个servlet将会没有任何响应的被返回。当等待的资源可用时,请求将会从新分配到这个servlet,使用一个新的线程,一个响应就会产生。
异步servlet最初是由Jetty的延续机制引进来的,这是Jetty的一个特殊的机制。Jetty7之后,延续的API已经被扩展成一个通用的API,将在任何servlet-3.0容器上进行异步工做,Jetty6,7,8一样支持。延续机制还能够在servlet 2.5 容器下以阻塞方式运行。
ContinuationSupport工厂能够经过request来得到一个延续实例:
Continuation continuation = ContinuationSupport.getContinuation(request);
为了挂起一个请求,挂起方法能够在延续上被调用:
void doGet(HttpServletRequest request, HttpServletResponse response) { ... // 可选择的: // continuation.setTimeout(long); continuation.suspend(); ... }
请求的生命周期将会从Servlet.service(...) 和Filter.doFilter(...) 的调用延续到容器的返回。当这些调用方法返回时,挂起的请求将不会被提交响应也不会被发送到客户端。
一旦请求被挂起,延续将会注册一个异步服务,这样等待的事件发生时异步服务将会被调用。
请求将会被挂起,直到continuation.resume()或continuation.complete()方法被调用。若是两个都没有被调用那么延续将会被超时。超时应该设置在挂起以前,经过continuation.setTimeout(long)这个方法,若是没有被设置,那么将使用默认的时间。若是没有超时监听着挂起延续,或者完成这个延续,那么调用continuation.isExpired()返回true。
挂起相似于servlet 3.0 的 request.startAsync()方法。不像jetty 6的延续,异常不会被抛出而且方法会正常返回。这容许注册的延续在挂起后发生避免发生互斥。若是一个须要一个异常(不知道延续而绕过代码并试图提交响应),那么continuation.undispatch() 方法将会被调用退出当前线程,并抛出一个ContinuationThrowable异常。
一旦异步事件发生,那么延续将被恢复:
void myAsyncCallback(Object results) { continuation.setAttribute("results",results); continuation.resume(); }
当延续被恢复,请求会被从新分配到这个servlet 容器,就像请求从新来过同样。然而在从新分配过程当中, continuation.isInitial()方法返回false,全部设置到异步处理器上的参数都是有效的。
延续的恢复相似于Servlet 3.0 的 AsyncContext.dispatch()方法。
当从新恢复一个请求时,异步处理器可能会生成响应,在写入响应后,处理器必须显示的经过调用方法代表延续完成了。
void myAsyncCallback(Object results) { writeResults(continuation.getServletResponse(),results); continuation.complete(); }
当完成方法被调用,容器会安排响应提交并刷新。延续的完成相似于Servlet 3.0 的 AsyncContext.complete()。
一个应用有可能经过ContinuationListener监听延续的各个状态。
void doGet(HttpServletRequest request, HttpServletResponse response) { ... Continuation continuation = ContinuationSupport.getContinuation(request); continuation.addContinuationListener(new ContinuationListener() { public void onTimeout(Continuation continuation) { ... } public void onComplete(Continuation continuation) { ... } }); continuation.suspend(); ... }
延续的监听相似于Servlet 3.0 的 AsyncListeners。
暂停/恢复模式用在当一个servlet 或 一个filter用来产生一个响应,在被异步处理器中断并恢复后。通常请求的属性用来传递结果,用来代表请求已经被暂停。
void doGet(HttpServletRequest request, HttpServletResponse response) { // 若是咱们须要得到异步的结果 Object results = request.getAttribute("results"); if (results==null) { final Continuation continuation = ContinuationSupport.getContinuation(request); // 若是没有超时 if (continuation.isExpired()) { sendMyTimeoutResponse(response); return; } // 恢复request continuation.suspend(); // 注册前一直被暂停 // 被异步服务注册,此处的代码将被服务调用 myAsyncHandler.register(new MyHandler() { public void onMyEvent(Object result) { continuation.setAttribute("results",results); continuation.resume(); } }); return; // 或者continuation.undispatch(); } // 发送结果 sendMyResultResponse(response,results); }
这是一个很是好的模式即当响应须要servlet容器的工具(如,使用了一个web框架)或者一个事件将恢复多个请求致使容器的线程池被多个处理器使用。
暂停/完成模式是用来当一个异步处理器被用来生成一个响应的状况:
void doGet(HttpServletRequest request, HttpServletResponse response) { final Continuation continuation = ContinuationSupport.getContinuation(request); // 若是没有超时 if (continuation.isExpired()) { sendMyTimeoutResponse(request,response); return; } // 将请求挂起 continuation.suspend(); // response 可能被包装 // r注册给异步服务,代码将被服务调用 myAsyncHandler.register(new MyHandler() { public void onMyEvent(Object result) { sendMyResultResponse(continuation.getServletResponse(),results); continuation.complete(); } }); }
这种模式在如下这种状况下是很是好的,即响应不须要容器的工具(如使用一种web框架)而且事件将会恢复一次延续。若是多个响应将被发送(例如,聊天室),那么写一次响应将会阻塞并在其余响应上致使一个DOS。
// // ======================================================================== // Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. // // The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php // // You may elect to redistribute this code under either of these licenses. // ======================================================================== // package com.acme; import java.io.IOException; import java.io.PrintWriter; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; import java.util.Queue; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.continuation.Continuation; import org.eclipse.jetty.continuation.ContinuationSupport; // 简单的异步聊天室 // 这不处理重复的用户名和同一个浏览器的标签状况 // 一些代码是重复的 public class ChatServlet extends HttpServlet { // 内部类用来保存每一个成员的消息队列 class Member { String _name; Continuation _continuation; Queue<String> _queue = new LinkedList<String>(); } Map<String,Map<String,Member>> _rooms = new HashMap<String,Map<String, Member>>(); // 处理浏览器的AJAX调用 @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // Ajax调用的编码形式 String action = request.getParameter("action"); String message = request.getParameter("message"); String username = request.getParameter("user"); if (action.equals("join")) join(request,response,username); else if (action.equals("poll")) poll(request,response,username); else if (action.equals("chat")) chat(request,response,username,message); } private synchronized void join(HttpServletRequest request,HttpServletResponse response,String username) throws IOException { Member member = new Member(); member._name=username; Map<String,Member> room=_rooms.get(request.getPathInfo()); if (room==null) { room=new HashMap<String,Member>(); _rooms.put(request.getPathInfo(),room); } room.put(username,member); response.setContentType("text/json;charset=utf-8"); PrintWriter out=response.getWriter(); out.print("{action:\"join\"}"); } private synchronized void poll(HttpServletRequest request,HttpServletResponse response,String username) throws IOException { Map<String,Member> room=_rooms.get(request.getPathInfo()); if (room==null) { response.sendError(503); return; } Member member = room.get(username); if (member==null) { response.sendError(503); return; } synchronized(member) { if (member._queue.size()>0) { // 发送一个聊天消息 response.setContentType("text/json;charset=utf-8"); StringBuilder buf=new StringBuilder(); buf.append("{\"action\":\"poll\","); buf.append("\"from\":\""); buf.append(member._queue.poll()); buf.append("\","); String message = member._queue.poll(); int quote=message.indexOf('"'); while (quote>=0) { message=message.substring(0,quote)+'\\'+message.substring(quote); quote=message.indexOf('"',quote+2); } buf.append("\"chat\":\""); buf.append(message); buf.append("\"}"); byte[] bytes = buf.toString().getBytes("utf-8"); response.setContentLength(bytes.length); response.getOutputStream().write(bytes); } else { Continuation continuation = ContinuationSupport.getContinuation(request); if (continuation.isInitial()) { // 没有消息时,挂起等待聊天或超时 continuation.setTimeout(20000); continuation.suspend(); member._continuation=continuation; } else { // 超时后发送空的响应 response.setContentType("text/json;charset=utf-8"); PrintWriter out=response.getWriter(); out.print("{action:\"poll\"}"); } } } } private synchronized void chat(HttpServletRequest request,HttpServletResponse response,String username,String message) throws IOException { Map<String,Member> room=_rooms.get(request.getPathInfo()); if (room!=null) { // 推送消息到全部成员 for (Member m:room.values()) { synchronized (m) { m._queue.add(username); // from m._queue.add(message); // chat // 若是轮询到则唤醒 if (m._continuation!=null) { m._continuation.resume(); m._continuation=null; } } } } response.setContentType("text/json;charset=utf-8"); PrintWriter out=response.getWriter(); out.print("{action:\"chat\"}"); } // 提供嵌入css和js的html服务 // 这应该是静态内容和真正使用的js库 @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { if (request.getParameter("action")!=null) doPost(request,response); else getServletContext().getNamedDispatcher("default").forward(request,response); } }
这个ChatServlet 例子,展现了挂起/恢复模式被用来创建一个聊天室(使用了异步servlet)。相同的原则将应用于cometd这样的框架,为这样的应用提供一个基于延续的丰富的环境。
// // ======================================================================== // Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. // // The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php // // You may elect to redistribute this code under either of these licenses. // ======================================================================== // package org.eclipse.jetty.servlets; import java.io.IOException; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import javax.servlet.AsyncContext; import javax.servlet.AsyncEvent; import javax.servlet.AsyncListener; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; /** * Quality of Service Filter. * <p> * This filter limits the number of active requests to the number set by the "maxRequests" init parameter (default 10). * If more requests are received, they are suspended and placed on priority queues. Priorities are determined by * the {@link #getPriority(ServletRequest)} method and are a value between 0 and the value given by the "maxPriority" * init parameter (default 10), with higher values having higher priority. * <p> * This filter is ideal to prevent wasting threads waiting for slow/limited * resources such as a JDBC connection pool. It avoids the situation where all of a * containers thread pool may be consumed blocking on such a slow resource. * By limiting the number of active threads, a smaller thread pool may be used as * the threads are not wasted waiting. Thus more memory may be available for use by * the active threads. * <p> * Furthermore, this filter uses a priority when resuming waiting requests. So that if * a container is under load, and there are many requests waiting for resources, * the {@link #getPriority(ServletRequest)} method is used, so that more important * requests are serviced first. For example, this filter could be deployed with a * maxRequest limit slightly smaller than the containers thread pool and a high priority * allocated to admin users. Thus regardless of load, admin users would always be * able to access the web application. * <p> * The maxRequest limit is policed by a {@link Semaphore} and the filter will wait a short while attempting to acquire * the semaphore. This wait is controlled by the "waitMs" init parameter and allows the expense of a suspend to be * avoided if the semaphore is shortly available. If the semaphore cannot be obtained, the request will be suspended * for the default suspend period of the container or the valued set as the "suspendMs" init parameter. * <p> * If the "managedAttr" init parameter is set to true, then this servlet is set as a {@link ServletContext} attribute with the * filter name as the attribute name. This allows context external mechanism (eg JMX via {@link ContextHandler#MANAGED_ATTRIBUTES}) to * manage the configuration of the filter. */ @ManagedObject("Quality of Service Filter") public class QoSFilter implements Filter { private static final Logger LOG = Log.getLogger(QoSFilter.class); static final int __DEFAULT_MAX_PRIORITY = 10; static final int __DEFAULT_PASSES = 10; static final int __DEFAULT_WAIT_MS = 50; static final long __DEFAULT_TIMEOUT_MS = -1; static final String MANAGED_ATTR_INIT_PARAM = "managedAttr"; static final String MAX_REQUESTS_INIT_PARAM = "maxRequests"; static final String MAX_PRIORITY_INIT_PARAM = "maxPriority"; static final String MAX_WAIT_INIT_PARAM = "waitMs"; static final String SUSPEND_INIT_PARAM = "suspendMs"; private final String _suspended = "QoSFilter@" + Integer.toHexString(hashCode()) + ".SUSPENDED"; private final String _resumed = "QoSFilter@" + Integer.toHexString(hashCode()) + ".RESUMED"; private long _waitMs; private long _suspendMs; private int _maxRequests; private Semaphore _passes; private Queue<AsyncContext>[] _queues; private AsyncListener[] _listeners; public void init(FilterConfig filterConfig) { int max_priority = __DEFAULT_MAX_PRIORITY; if (filterConfig.getInitParameter(MAX_PRIORITY_INIT_PARAM) != null) max_priority = Integer.parseInt(filterConfig.getInitParameter(MAX_PRIORITY_INIT_PARAM)); _queues = new Queue[max_priority + 1]; _listeners = new AsyncListener[_queues.length]; for (int p = 0; p < _queues.length; ++p) { _queues[p] = new ConcurrentLinkedQueue<>(); _listeners[p] = new QoSAsyncListener(p); } int maxRequests = __DEFAULT_PASSES; if (filterConfig.getInitParameter(MAX_REQUESTS_INIT_PARAM) != null) maxRequests = Integer.parseInt(filterConfig.getInitParameter(MAX_REQUESTS_INIT_PARAM)); _passes = new Semaphore(maxRequests, true); _maxRequests = maxRequests; long wait = __DEFAULT_WAIT_MS; if (filterConfig.getInitParameter(MAX_WAIT_INIT_PARAM) != null) wait = Integer.parseInt(filterConfig.getInitParameter(MAX_WAIT_INIT_PARAM)); _waitMs = wait; long suspend = __DEFAULT_TIMEOUT_MS; if (filterConfig.getInitParameter(SUSPEND_INIT_PARAM) != null) suspend = Integer.parseInt(filterConfig.getInitParameter(SUSPEND_INIT_PARAM)); _suspendMs = suspend; ServletContext context = filterConfig.getServletContext(); if (context != null && Boolean.parseBoolean(filterConfig.getInitParameter(MANAGED_ATTR_INIT_PARAM))) context.setAttribute(filterConfig.getFilterName(), this); } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { boolean accepted = false; try { Boolean suspended = (Boolean)request.getAttribute(_suspended); if (suspended == null) { accepted = _passes.tryAcquire(getWaitMs(), TimeUnit.MILLISECONDS); if (accepted) { request.setAttribute(_suspended, Boolean.FALSE); if (LOG.isDebugEnabled()) LOG.debug("Accepted {}", request); } else { request.setAttribute(_suspended, Boolean.TRUE); int priority = getPriority(request); AsyncContext asyncContext = request.startAsync(); long suspendMs = getSuspendMs(); if (suspendMs > 0) asyncContext.setTimeout(suspendMs); asyncContext.addListener(_listeners[priority]); _queues[priority].add(asyncContext); if (LOG.isDebugEnabled()) LOG.debug("Suspended {}", request); return; } } else { if (suspended) { request.setAttribute(_suspended, Boolean.FALSE); Boolean resumed = (Boolean)request.getAttribute(_resumed); if (resumed == Boolean.TRUE) { _passes.acquire(); accepted = true; if (LOG.isDebugEnabled()) LOG.debug("Resumed {}", request); } else { // Timeout! try 1 more time. accepted = _passes.tryAcquire(getWaitMs(), TimeUnit.MILLISECONDS); if (LOG.isDebugEnabled()) LOG.debug("Timeout {}", request); } } else { // Pass through resume of previously accepted request. _passes.acquire(); accepted = true; if (LOG.isDebugEnabled()) LOG.debug("Passthrough {}", request); } } if (accepted) { chain.doFilter(request, response); } else { if (LOG.isDebugEnabled()) LOG.debug("Rejected {}", request); ((HttpServletResponse)response).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE); } } catch (InterruptedException e) { ((HttpServletResponse)response).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE); } finally { if (accepted) { for (int p = _queues.length - 1; p >= 0; --p) { AsyncContext asyncContext = _queues[p].poll(); if (asyncContext != null) { ServletRequest candidate = asyncContext.getRequest(); Boolean suspended = (Boolean)candidate.getAttribute(_suspended); if (suspended == Boolean.TRUE) { candidate.setAttribute(_resumed, Boolean.TRUE); asyncContext.dispatch(); break; } } } _passes.release(); } } } /** * Computes the request priority. * <p> * The default implementation assigns the following priorities: * <ul> * <li> 2 - for an authenticated request * <li> 1 - for a request with valid / non new session * <li> 0 - for all other requests. * </ul> * This method may be overridden to provide application specific priorities. * * @param request the incoming request * @return the computed request priority */ protected int getPriority(ServletRequest request) { HttpServletRequest baseRequest = (HttpServletRequest)request; if (baseRequest.getUserPrincipal() != null) { return 2; } else { HttpSession session = baseRequest.getSession(false); if (session != null && !session.isNew()) return 1; else return 0; } } public void destroy() { } /** * Get the (short) amount of time (in milliseconds) that the filter would wait * for the semaphore to become available before suspending a request. * * @return wait time (in milliseconds) */ @ManagedAttribute("(short) amount of time filter will wait before suspending request (in ms)") public long getWaitMs() { return _waitMs; } /** * Set the (short) amount of time (in milliseconds) that the filter would wait * for the semaphore to become available before suspending a request. * * @param value wait time (in milliseconds) */ public void setWaitMs(long value) { _waitMs = value; } /** * Get the amount of time (in milliseconds) that the filter would suspend * a request for while waiting for the semaphore to become available. * * @return suspend time (in milliseconds) */ @ManagedAttribute("amount of time filter will suspend a request for while waiting for the semaphore to become available (in ms)") public long getSuspendMs() { return _suspendMs; } /** * Set the amount of time (in milliseconds) that the filter would suspend * a request for while waiting for the semaphore to become available. * * @param value suspend time (in milliseconds) */ public void setSuspendMs(long value) { _suspendMs = value; } /** * Get the maximum number of requests allowed to be processed * at the same time. * * @return maximum number of requests */ @ManagedAttribute("maximum number of requests to allow processing of at the same time") public int getMaxRequests() { return _maxRequests; } /** * Set the maximum number of requests allowed to be processed * at the same time. * * @param value the number of requests */ public void setMaxRequests(int value) { _passes = new Semaphore((value - getMaxRequests() + _passes.availablePermits()), true); _maxRequests = value; } private class QoSAsyncListener implements AsyncListener { private final int priority; public QoSAsyncListener(int priority) { this.priority = priority; } @Override public void onStartAsync(AsyncEvent event) throws IOException { } @Override public void onComplete(AsyncEvent event) throws IOException { } @Override public void onTimeout(AsyncEvent event) throws IOException { // Remove before it's redispatched, so it won't be // redispatched again at the end of the filtering. AsyncContext asyncContext = event.getAsyncContext(); _queues[priority].remove(asyncContext); asyncContext.dispatch(); } @Override public void onError(AsyncEvent event) throws IOException { } } }
这个QoSFilter(这是jetty-servlets包中的一个类的),在过滤器内限制请求数量并使用挂起/恢复风格。这个能够用来保护JDBC链接池,或者限制对其余有限资源的访问。
这个DosFilter (这是jetty-servlets包中的一个类的,源码再也不贴出)例子和QoSFilter比较类似,可是以拒绝服务攻击的方式来保护web应用,尽量从应用程序内进行保护。
若是检测到同一个源的大量请求,那么这些请求将会被挂起并生成一个警告。这假设攻击者使用简单的阻塞方式来攻击,因此暂停你想保护的他们想访问的资源。真正有效避免DOS攻击应该交于网络设备。
// // ======================================================================== // Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. // // The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php // // You may elect to redistribute this code under either of these licenses. // ======================================================================== // package org.eclipse.jetty.proxy; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.util.concurrent.TimeUnit; import javax.servlet.AsyncContext; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.ContentProvider; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.client.util.InputStreamContentProvider; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.util.Callback; /** * <p>Servlet 3.0 asynchronous proxy servlet.</p> * <p>The request processing is asynchronous, but the I/O is blocking.</p> * * @see AsyncProxyServlet * @see AsyncMiddleManServlet * @see ConnectHandler */ public class ProxyServlet extends AbstractProxyServlet { @Override protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { final int requestId = getRequestId(request); String rewrittenTarget = rewriteTarget(request); if (_log.isDebugEnabled()) { StringBuffer uri = request.getRequestURL(); if (request.getQueryString() != null) uri.append("?").append(request.getQueryString()); if (_log.isDebugEnabled()) _log.debug("{} rewriting: {} -> {}", requestId, uri, rewrittenTarget); } if (rewrittenTarget == null) { onProxyRewriteFailed(request, response); return; } final Request proxyRequest = getHttpClient().newRequest(rewrittenTarget) .method(request.getMethod()) .version(HttpVersion.fromString(request.getProtocol())); copyRequestHeaders(request, proxyRequest); addProxyHeaders(request, proxyRequest); final AsyncContext asyncContext = request.startAsync(); // We do not timeout the continuation, but the proxy request asyncContext.setTimeout(0); proxyRequest.timeout(getTimeout(), TimeUnit.MILLISECONDS); if (hasContent(request)) proxyRequest.content(proxyRequestContent(request, response, proxyRequest)); sendProxyRequest(request, response, proxyRequest); } protected ContentProvider proxyRequestContent(HttpServletRequest request, HttpServletResponse response, Request proxyRequest) throws IOException { return new ProxyInputStreamContentProvider(request, response, proxyRequest, request.getInputStream()); } @Override protected Response.Listener newProxyResponseListener(HttpServletRequest request, HttpServletResponse response) { return new ProxyResponseListener(request, response); } protected void onResponseContent(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, byte[] buffer, int offset, int length, Callback callback) { try { if (_log.isDebugEnabled()) _log.debug("{} proxying content to downstream: {} bytes", getRequestId(request), length); response.getOutputStream().write(buffer, offset, length); callback.succeeded(); } catch (Throwable x) { callback.failed(x); } } /** * <p>Convenience extension of {@link ProxyServlet} that offers transparent proxy functionalities.</p> * * @see org.eclipse.jetty.proxy.AbstractProxyServlet.TransparentDelegate */ public static class Transparent extends ProxyServlet { private final TransparentDelegate delegate = new TransparentDelegate(this); @Override public void init(ServletConfig config) throws ServletException { super.init(config); delegate.init(config); } @Override protected String rewriteTarget(HttpServletRequest request) { return delegate.rewriteTarget(request); } } protected class ProxyResponseListener extends Response.Listener.Adapter { private final HttpServletRequest request; private final HttpServletResponse response; protected ProxyResponseListener(HttpServletRequest request, HttpServletResponse response) { this.request = request; this.response = response; } @Override public void onBegin(Response proxyResponse) { response.setStatus(proxyResponse.getStatus()); } @Override public void onHeaders(Response proxyResponse) { onServerResponseHeaders(request, response, proxyResponse); } @Override public void onContent(final Response proxyResponse, ByteBuffer content, final Callback callback) { byte[] buffer; int offset; int length = content.remaining(); if (content.hasArray()) { buffer = content.array(); offset = content.arrayOffset(); } else { buffer = new byte[length]; content.get(buffer); offset = 0; } onResponseContent(request, response, proxyResponse, buffer, offset, length, new Callback.Nested(callback) { @Override public void failed(Throwable x) { super.failed(x); proxyResponse.abort(x); } }); } @Override public void onComplete(Result result) { if (result.isSucceeded()) onProxyResponseSuccess(request, response, result.getResponse()); else onProxyResponseFailure(request, response, result.getResponse(), result.getFailure()); if (_log.isDebugEnabled()) _log.debug("{} proxying complete", getRequestId(request)); } } protected class ProxyInputStreamContentProvider extends InputStreamContentProvider { private final HttpServletResponse response; private final Request proxyRequest; private final HttpServletRequest request; protected ProxyInputStreamContentProvider(HttpServletRequest request, HttpServletResponse response, Request proxyRequest, InputStream input) { super(input); this.request = request; this.response = response; this.proxyRequest = proxyRequest; } @Override public long getLength() { return request.getContentLength(); } @Override protected ByteBuffer onRead(byte[] buffer, int offset, int length) { if (_log.isDebugEnabled()) _log.debug("{} proxying content to upstream: {} bytes", getRequestId(request), length); return onRequestContent(request, proxyRequest, buffer, offset, length); } protected ByteBuffer onRequestContent(HttpServletRequest request, Request proxyRequest, byte[] buffer, int offset, int length) { return super.onRead(buffer, offset, length); } @Override protected void onReadFailure(Throwable failure) { onClientRequestFailure(request, proxyRequest, response, failure); } } }
这个ProxyServlet(这是jetty-proxy包中的一个类的)例子使用挂起/完成模式,Jetty异步客户端实现一个可扩展的代理服务器。
你能够在代码中组装和配置Jetty或者彻底使用Spring的IOC框架。若是你想作的仅仅是将你的Jetty服务嵌入到Spring中,那么能够简单的看下面例子的xml片断。若是你想使用spring来替换jetty-xml 用于启动一个普通的Jetty应用,你能够这么作可是将不会依赖其余的系统框架。
Jetty spring模块的功能是能够经过该模块启动Jetty。例如:
$ java -jar start.jar --add-to-startd=spring
这(或者使用 --add-to-start=spring命令)将建立一个${jetty.home}/lib/spring目录,并将jetty-spring的jar包放入其中。可是不提供spring的jar包及其依赖包。你须要本身下载它们并将它们放到Jetty的classpath下 - 你可使用由spring.mod建立的 ${jetty.home}/lib/spring 目录存放这些jar。
经过spring配置Jetty是很是简单的经过调用spring APIs将其做为一个bean。下面是一个例子模仿默认jetty启动配置。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <!-- =============================================================== --> <!-- Configure the Jetty Server with Spring --> <!-- This file is the similar to jetty.xml, but written in spring --> <!-- XmlBeanFactory format. --> <!-- =============================================================== --> <beans> <bean id="contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection"/> <bean id="server" name="Main" class="org.eclipse.jetty.server.Server" init-method="start" destroy-method="stop"> <constructor-arg> <bean id="threadPool" class="org.eclipse.jetty.util.thread.QueuedThreadPool"> <property name="minThreads" value="10"/> <property name="maxThreads" value="50"/> </bean> </constructor-arg> <property name="connectors"> <list> <bean id="connector" class="org.eclipse.jetty.server.ServerConnector"> <constructor-arg ref="server"/> <property name="port" value="8080"/> </bean> </list> </property> <property name="handler"> <bean id="handlers" class="org.eclipse.jetty.server.handler.HandlerCollection"> <property name="handlers"> <list> <ref bean="contexts"/> <bean id="defaultHandler" class="org.eclipse.jetty.server.handler.DefaultHandler"/> </list> </property> </bean> </property> <property name="beans"> <list> <bean id="deploymentManager" class="org.eclipse.jetty.deploy.DeploymentManager"> <property name="contexts" ref="contexts"/> <property name="appProviders"> <list> <bean id="webAppProvider" class="org.eclipse.jetty.deploy.providers.WebAppProvider"> <property name="monitoredDirName" value="webapps"/> <property name="scanInterval" value="1"/> <property name="extractWars" value="true"/> </bean> </list> </property> </bean> </list> </property> </bean> </beans>
Jetty OSGi基础设施提供了一个内置有OSGi容器的Jetty容器内。传统的JavaEE web应用能够被部署,除此以外还有Jetty的与OSGi一块儿的ContextHandlers。此外,基础设施还支持OSGi HttpService接口。
全部的Jetty jar包含清单条目确保他们能够部署为一个OSGi容器包。您将须要安装一些jetty jar到OSGi容器中。你老是能够在maven仓库中得到Jetty的jar包,或者你能够在下载的Jetty软件包中获取。这里有一个Jetty jar包的最小集合:
Jar | 包名称 |
---|---|
jetty-util |
org.eclipse.jetty.util |
jetty-http |
org.eclipse.jetty.http |
jetty-io |
org.eclipse.jetty.io |
jetty-security |
org.eclipse.jetty.security |
jetty-server |
org.eclipse.jetty.server |
jetty-servlet |
org.eclipse.jetty.servlet |
jetty-webapp |
org.eclipse.jetty.webapp |
jetty-deploy |
org.eclipse.jetty.deploy |
jetty-xml |
org.eclipse.jetty.xml |
jetty-osgi-servlet-api |
org.eclipse.jetty.toolchain |
你也一样会须要OSGi事件管理服务和OSGi配置管理服务。若是你的OSGi容器没有自动将这些服务启用,那么你须要以适当的方式将它们添加到容器中。
如今你已经安装好Jetty最基本的jar包,那么你须要继续安装jetty-osgi-boot.jar包,在Maven仓库中下载,点击我(http://central.maven.org/maven2/org/eclipse/jetty/osgi/jetty-osgi-boot/)。
当它启动时,这个包将会实例化并在Jetty的OSGi容器可用。若是这个包没有自动安装到OSGi容器中,那么你应该使用命令手动将其安装到容器的中。
在安装以前,你可能但愿定制一下Jetty容器。一般这应该由一些系统属性个组合和一般的jetty xml配置文件来完成。定义系统属性的方式依赖于您使用OSGi容器,因此确保你熟悉如何设置您的环境。在下面的例子中,咱们将假定OSGi容器容许咱们将系统属性设置为简单的名称=值对。
可用的系统属性有:
jetty.http.port
若是没有特别指定,默认8080。
jetty.home
这个属性或者 jetty.home.bundle必须被指定。这个属性应该指向包含配置xml文件的一个文件系统位置。例如:
jetty.home=/opt/custom/jetty
/opt/custom/jetty文件夹包含:
etc/jetty.xml
etc/jetty-selector.xml
etc/jetty-deployer.xml
etc/jetty-special.xml
jetty.home.bundle
这个属性或jetty.home属性必须被配置。这个属性应该指定一个包含 jettyhome/ 文件夹的包名。 jettyhome/ 文件夹必须有一个名为 etc/ (包含启动的xml配置文件)的子文件夹。 jetty-osgi-boot.jar里面包含 jettyhome/文件夹,并有默认的xml配置文件。下面展现如何指定它:
jetty.home.bundle=org.eclipse.jetty.osgi.boot
这个jar包,有以下配置文件:
META-INF/MANIFEST.MF
jettyhome/etc/jetty.xml
jettyhome/etc/jetty-deployer.xml
jettyhome/etc/jetty-http.xml
jetty.etc.config.urls
这个属性指定xml文件的路径。若是没有指定则默认为:
etc/jetty.xml,etc/jetty-http.xml,etc/jetty-deployer.xml
如今你能够将jetty-osgi-boot.jar部署到你的OSGi容器中了。一个Jetty的server实例将会被建立,xml配置文件的配置将会应用在上面,而后它将作为一个OSGi服务发布。一般,你不须要与服务的实例进行交互,然而你仍然能够经过使用OSGi API的引用来得到Jetty。
org.osgi.framework.BundleContext bc;
org.osgi.framework.ServiceReference ref = bc.getServiceReference("org.eclipse.jetty.server.Server");
Server服务有一些与之相关的属性,你能够经过org.osgi.framework.ServiceReference.getProperty(String) 方法来得到:
managedServerName
由 jetty-osgi-boot.jar建立的Jetty实例,被称为“默认Jetty服务”
jetty.etc.config.urls
在jetty.home 或 jetty.home.bundle/jettyhome下的xml配置url列表
正如咱们在前面章节中所看到的,jetty-osgi-boot的代码将会经过jetty.etc.config.urls 指定的xml配置文件建立一个 org.eclipse.jetty.server.Server实例,而后将其注册为一个OSGi服务。默认实例的默认名称为“defaultJettyServer”
你也能够建立另外的应用实例,而后注册为OSGi服务,jetty-osgi-boot代码会发现它们,而后配置它们,这样它们就能够部署ContextHandlers 和webapp包。当你部署webapps 或者ContextHandlers 作为一个包或者一个服务(看下面的章节),你能够经过服务器的名称针对他们被部署到一个特定的服务器实例。
这里有一个例子说明如何建立一个新的server实例而且注册到OSGi这样jetty-osgi-boot代码会发现它、配置它,这样它就能够部署目标:
public class Activator implements BundleActivator { public void start(BundleContext context) throws Exception { Server server = new Server(); // 在这里对服务进行任何配置 String serverName = "fooServer"; Dictionary serverProps = new Hashtable(); // 为当前服务定义一个惟一的服务名 serverProps.put("managedServerName", serverName); // 为当前服务设置一个惟一的端口 serverProps.put("jetty.http.port", "9999"); // 让Jetty应用一些配置文件到这个实例上 serverProps.put("jetty.etc.config.urls", "file:/opt/jetty/etc/jetty.xml,file:/opt/jetty/etc/jetty-selector.xml,file:/opt/jetty/etc/jetty-deployer.xml"); // 作为一个OSGi服务,使之被发现 context.registerService(Server.class.getName(), server, serverProps); } }
如今咱们能够建立一个名为"fooServer"的服务,咱们能够部署这个webapps和ContextHandlers作为一个包或者服务。这里有一个例子关于部署一个webapp作为一个服务而且将其定位到上面建立的"fooServer"服务:
public class Activator implements BundleActivator { public void start(BundleContext context) throws Exception { // 建立一个webapp并指向"fooServer WebAppContext webapp = new WebAppContext(); Dictionary props = new Hashtable(); props.put("war", "."); props.put("contextPath", "/acme"); props.put("managedServerName", "fooServer"); context.registerService(ContextHandler.class.getName(), webapp, props); } }
OSGi 容器监听已经安装的Bundles ,并将其部署到已有的webapp上。
任何符合下面条件的将会作为webapp进行部署:
包含一个WEB-INF/web.xml文件的Bundle
若是一个bundle 包含一个web应用描述,那么它将会被自动部署。这是一个简单的方法来部署经典JavaEE webapps。Bundle 的 MANIFEST 包含Jetty-WarFolderPath(以前的版本为jetty-9.3)或Jetty-WarResourcePath,这是bundle 里面关于webapp资源的位置。一般这是有用的尤为当bundle 不是一个纯粹的webapp,而是bundle的一个组件的时候。这里有一个webapp资源的路径不是bundle根路径的例子,而是在web/MANIFEST文件中:
Bundle-Name: Web
Jetty-WarResourcePath: web
Import-Package: javax.servlet;version="3.1",
javax.servlet.resources;version="3.1"
Bundle-SymbolicName: com.acme.sample.web
Bundle 包含:
META-INF/MANIFEST.MF
web/index.html
web/foo.html
web/WEB-INF/web.xml
com/acme/sample/web/MyStuff.class
com/acme/sample/web/MyOtherStuff.class
Bundle MANIFEST文件包含Web-ContextPath属性
这个头是在OSGi中使用webapp的RFC-66 规范的一部分。这里有一个基于前面的例子,使用Web-ContextPath头来设置部署context path为 /sample。MANIFEST以下:
Bundle-Name: Web
Jetty-WarResourcePath: web
Web-ContextPath: /sample
Import-Package: javax.servlet;version="3.1",
javax.servlet.resources;version="3.1"
Bundle-SymbolicName: com.acme.sample.web
你也能够在你的bundle的MANIFEST中定义额外的头信息用来帮助应用程序部署:
Jetty-defaultWebXmlFilePath
用来部署webapp的webdefault.xml的路径。这个路径能够是绝对路径(绝对路径或file: url)或者相对路径(相对于bundle 根路径)。默认的webdefault.xml 文件将后安装到OSGi容器中。
Jetty-WebXmlFilePath
web.xml文件的地址。这个路径能够是绝对路径(绝对路径或file: url)或者相对路径(相对于bundle 根路径)。默认为WEB-INF/web.xml。
Jetty-extraClassPath
新增到webapp的类加载器中额外的类路径。
Jetty-bundleInstall
基础文件夹的路径用来覆盖已经安装的bundled - 主要用于那些默认方式解压的OSGi框架。
Require-TldBundle
webapp依赖的全部包含TLD的bundle,以逗号分割的列表。
managedServerName
部署webapp bundle的服务实例名。若是没有指定,那么默认为 "defaultJettyServer"。
Jetty-WarFragmentResourcePath
包含webapp静态资源的在web-bundle中的路径。这些路径将会追加到webapp的基础资源路径下。
Jetty-WarPrependFragmentResourcePath
包含webapp静态资源的在web-bundle中的路径。这些路径将会追加到webapp的基础资源路径下。
Jetty-ContextFilePath
将要部署到webapp中的bundle路径列表。或者可能包含一个单独的Jetty context文件叫作"jetty-webapp-context.xml",在webapp 的bundle的META-INF文件夹下,它将会自动部署到webapp中。
正如咱们所看到的在前面的小节中,若是一个bundled的MANIFEST 文件包含RFC-66规范的头属性Web-ContextPath,那么Jetty将会使用这个作为上下文路径。若是MANIFEST 文件中没有这个头信息,那么Jetty将会根据最后一个bundle元素的地址(经过Bundle.getLocation()方法,并去掉扩展名)编造一个上下文路径。
例如,咱们有一个bundle,路径以下:
file://some/where/over/the/rainbow/oz.war
那么生成的上下文路径将是:
/oz
你能够经过Jetty的xml文件来进一步定制你的webapp。这些xml文件必须放置在bundle的META-INF下面,并且名称必须为jetty-webapp-context.xml。
这里有一个webapp bundle例子,包含以下文件:
META-INF/MANIFEST.MF
META-INF/jetty-webapp-context.xml
web/index.html
web/foo.html
web/WEB-INF/web.xml
com/acme/sample/web/MyStuff.class
com/acme/sample/web/MyOtherStuff.class
这里还有一个META-INF/jetty-webapp-context.xml 文件的例子:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd"> <Configure class="org.eclipse.jetty.webapp.WebAppContext"> <Set name="defaultsDescriptor"><Property name="bundle.root"/>META-INF/webdefault.xml</Set> </Configure>
正如你所看到的,这是一个普通的上下文xml文件,用来设置webapp。然而,有一些额外的有用的属性,能够引用:
Server
这是一个Jetty org.eclipse.jetty.server的引用。在这个context xml文件配置的Server实例将会被部署。
bundle.root
这是一个org.eclipse.jetty.util.resource.Resource的引用,用来表明Bundle路径。这个能够是一个文件系统的文件夹,或者是一个jar文件的地址。
为了部署一个webapps,Jetty OSGi容器监听全部安装的bundle,不使用重量级的webapp而是使用灵活的ContextHandlers概念的Jetty-specific。
根据下面的标准来决定是否部署为一个ContextHandler:
Bundel的MANIFEST 包含Jetty-ContextFilePath属性
一系列context文件的名字 - 每个表明一个ContextHandler都将会部署到Jetty。context文件能够在bundle里面,也能够在文件系统的任何位置,或者在jetty.home文件夹下。一个在bundle里面的context文件的配置:
Jetty-ContextFilePath: ./a/b/c/d/foo.xml
一个在文件系统的context文件配置:
Jetty-ContextFilePath: /opt/app/contexts/foo.xml
一个相对于jetty.home路径的context文件配置:
Jetty-ContextFilePath: contexts/foo.xml
多个不一样的context文件配置:
Jetty-ContextFilePath: ./a/b/c/d/foo.xml,/opt/app/contexts/foo.xml,contexts/foo.xml
其它可用来配置部署ContextHandler的属性有:
managedServerName
用来部署webapp bundle的Server实例的名称。若是没有特别指定,默认的名称为"defaultJettyServer"。
一般ContextHandler的上下文路径在context的xml文件中进行配置。然而你仍然能够经过使用MANIFEST文件中的Web-ContextPath属性进行覆盖。
在Jetty OSGi容器将发现于MANIFEST文件头信息应用于context xml文件中以前,能够在文件中设置一些有用的属性:
Server
这是一个Jetty org.eclipse.jetty.server的引用。在这个context xml文件配置的Server实例将会被部署。
bundle.root
这是一个org.eclipse.jetty.util.resource.Resource的引用,用来表明Bundle路径。这个能够是一个文件系统的文件夹,或者是一个jar文件的地址。
这里有一个context xml文件的例子,应用了这些属性:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd"> <Configure class="org.eclipse.jetty.server.handler.ContextHandler"> <!-- Get root for static content, could be on file system or this bundle --> <Call id="res" class="org.eclipse.jetty.util.resource.Resource" name="newResource"> <Arg><Property name="bundle.root"/></Arg> </Call> <Ref refid="res"> <Call id="base" name="addPath"> <Arg>/static/</Arg> </Call> </Ref> <Set name="contextPath">/unset</Set> <!-- 设置相对于bundle的静态文件的基本资源 --> <Set name="baseResource"> <Ref refid="base"/> </Set> <Set name="handler"> <New class="org.eclipse.jetty.server.handler.ResourceHandler"> <Set name="welcomeFiles"> <Array type="String"> <Item>index.html</Item> </Array> </Set> <Set name="cacheControl">max-age=3600,public</Set> </New> </Set> </Configure>
为了部署而去监听MANIFEST 文件中定义的webapp 或ContextHandler ,Jetty OSGi容器也会监听已经注册的是org.eclipse.jetty.webapp.WebAppContext实例的OSGi服务。因此你能够编程建立一个WebAppContext,把它注册为一个服务,而后让Jetty发现并部署它。
这里有一个例子,部署了一个静态资源包,是一个org.osgi.framework.BundleActivator实例的WebAppContext:
包中的资源有:
META-INF/MANIFEST.MF
index.html
com/acme/osgi/Activator.class
MANIFEST.MF文件的内容为:
Bundle-Classpath: .
Bundle-Name: Jetty OSGi Test WebApp
DynamicImport-Package: org.eclipse.jetty.*;version="[9.0,10.0)"
Bundle-Activator: com.acme.osgi.Activator
Import-Package: org.eclipse.jetty.server.handler;version="[9.0,10)",
org.eclipse.jetty.webapp;version="[9.0,10)",
org.osgi.framework;version= "[1.5,2)",
org.osgi.service.cm;version="1.2.0",
org.osgi.service.packag eadmin;version="[1.2,2)",
org.osgi.service.startlevel;version="1.0.0",
org.osgi.service.url;version="1.0.0",
org.osgi.util.tracker;version= "1.3.0",
org.xml.sax,org.xml.sax.helpers
Bundle-SymbolicName: com.acme.testwebapp
主要的代码为:
public void start(BundleContext context) throws Exception { WebAppContext webapp = new WebAppContext(); Dictionary props = new Hashtable(); props.put("Jetty-WarResourcePath","."); props.put("contextPath","/acme"); context.registerService(ContextHandler.class.getName(),webapp,props); }
上面的安装信息足够Jetty来识别并部署一个WebAppContext 到/acme下。
就像上面的例子展现的那样,你可使用OSGi服务属性来对Jetty进行额外的配置:
同部署WebAppContexts类似,Jetty的OSGi容器能够侦测注册的ContextHandler的OSGi服务并确保它们被部署。ContextHandler能够在注册为一个OSGi服务前被彻底配置好 - 在这种状况下,Jetty 的OSGi容器仅仅部署它 - 或者ContextHandler能够配特别的配置,这种状况下Jetty 的OSGi容器彻底经过context的xml配置文件或者属性文件来配置。
这里有一个例子部署一个org.osgi.framework.BundleActivator实例的静态资源包,并注册为OSGi服务,传递定义在context的xml配置文件中的属性用于部署:
包内容以下:
META-INF/MANIFEST.MF
static/index.html
acme.xml
com/acme/osgi/Activator.class
com/acme/osgi/Activator$1.class
MANIFEST.MF文件内容以下:
Bundle-Classpath: .
Bundle-Name: Jetty OSGi Test Context
DynamicImport-Package: org.eclipse.jetty.*;version="[9.0,10.0)"
Bundle-Activator: com.acme.osgi.Activator
Import-Package: javax.servlet;version="2.6.0",
javax.servlet.resources;version="2.6.0",
org.eclipse.jetty.server.handler;version="[9.0,10)",
org.osgi.framework;version="[1.5,2)",
org.osgi.service.cm;version="1.2.0",
org.osgi.service.packageadmin;version="[1.2,2)",
org.osgi.service.startlevel;version="1.0.0.o",
org.osgi.service.url;version="1.0.0",
org.osgi.util.tracker;version="1.3.0",
org.xml.sax,org.xml.sax.helpers
Bundle-SymbolicName: com.acme.testcontext
主要的代码:
public void start(final BundleContext context) throws Exception { ContextHandler ch = new ContextHandler(); ch.addEventListener(new ServletContextListener () { @Override public void contextInitialized(ServletContextEvent sce) { System.err.println("Context is initialized"); } @Override public void contextDestroyed(ServletContextEvent sce) { System.err.println("Context is destroyed!"); } }); Dictionary props = new Hashtable(); props.put("Web-ContextPath","/acme"); props.put("Jetty-ContextFilePath", "acme.xml"); context.registerService(ContextHandler.class.getName(),ch,props); }
acme.xml配置文件的内容:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd"> <Configure class="org.eclipse.jetty.server.handler.ContextHandler"> <!-- 静态资源根路径,能够是文件系统或者安装包 --> <Call id="res" class="org.eclipse.jetty.util.resource.Resource" name="newResource"> <Arg><Property name="bundle.root"/></Arg> </Call> <Ref refid="res"> <Call id="base" name="addPath"> <Arg>/static/</Arg> </Call> </Ref> <Set name="contextPath">/unset</Set> <!-- 设置静态资源根路径到包内 --> <Set name="baseResource"> <Ref refid="base"/> </Set> <Set name="handler"> <New class="org.eclipse.jetty.server.handler.ResourceHandler"> <Set name="welcomeFiles"> <Array type="String"> <Item>index.html</Item> </Array> </Set> <Set name="cacheControl">max-age=3600,public</Set> </New> </Set> </Configure>
你也可使用以下的OSGi服务的属性:
managedServerName
部署的webapp的Server实例的名称。若是没有指定,默认的Server实例名称为"defaultJettyServer"。
额外的可配置的在context 文件的属性
在Jetty的OSGi容器应用一个在Jetty-ContextFilePath属性下发现的context的xml文件时,它能够设置一些有用的属性在xml文件中:
Server
这个引用Jetty org.eclipse.jetty.server.Server实例,及即将应用context的xml文件部署的ContextHandler 。
bundle.root
这个引用org.eclipse.jetty.util.resource.Resource实例,表明着作为一个服务部署ContextHandler包的路径(经过Bundle.getLocation()来得到)。这个能够是一个在文件系统的文件夹若是OSGi容器能够自动解压包文件,或者是一个jar包路径,若是包文件不被解压。
在上面的例子中你能够看到这两个属性同时应用的场景在context的xml文件中。
Jetty OSGi容器对于 WebAppContexts 和 ContextHandlers 实现企业规范v4.2,在前面的章节已介绍经过包或者OSGi服务的形式进行部署。
Context属性
对于每个WebAppContext或ContextHandler,下面的属性是必须的:
Service 属性
规范要求,每个被Jetty OSGi容器部署的WebAppContext或ContextHandler,同时必须发布为OSGi服务(除非它已是一个OSGi服务),下面的属性与服务有关:
OSGi 事件
根据规范要求,下面的事件须要被公布:
为了在你的webapp和应用包中使用JSP,你须要按照JSP和JSTL的jar包和它们的依赖包到你的OSGi容器中。一些你能够在Jetty程序包中发现,固然也有些须要从Maven仓库中下载。这里有一个须要的jar包的列表。
Table 29.2. JSP须要的jar包
Jar包 | 包名 | 路径 |
---|---|---|
The annotation jars |
||
org.mortbay.jasper:apache-el |
org.mortbay.jasper.apache-el |
Distribution lib/apache-jsp |
org.mortbay.jasper:apache-jsp |
org.mortbay.jasper.apache-jsp |
Distribution lib/apache-jsp |
org.eclipse.jetty:apache-jsp |
org.eclipse.jetty.apache-jsp |
Distribution lib/apache-jsp |
org.eclipse.jdt.core-3.8.2.v20130121.jar |
org.eclipse.jdt.core.compiler.batch |
Distribution lib/apache-jsp |
org.eclipse.jetty.osgi:jetty-osgi-boot-jsp |
org.eclipse.jetty.osgi.boot.jsp |
Maven central |
对于JSTL库,咱们建议使用Glassfish的实现,它有一些简单的依赖以下:
Table 29.3. Glassfish JSTL须要的jar包
Jar包 | 包名 | 路径 |
---|---|---|
The jsp jars |
||
org.eclipse.jetty.orbit:javax.servlet.jsp.jstl-1.2.0.v201105211821.jar |
javax.servlet.jsp.jstl |
Distribution lib/jsp |
org.glassfish.web:javax.servlet.jsp.jstl-1.2.2.jar |
org.glassfish.web.javax.servlet.jsp.jstl |
Distribution lib/jsp |
固然,你也可使用Apache的JSTL实现,固然也须要一些依赖:
Table 29.4. Apache JSTL须要的jar包
Jar包 | 包名 | 路径 |
---|---|---|
The jsp jars |
||
org.apache.taglibs:taglibs-standard-spec:jar:1.2.1 |
org.apache.taglibs.taglibs-standard-spec |
Distribution lib/apache-jstl |
org.apache.taglibs:taglibs-standard-spec:jar:1.2.1 |
org.apache.taglibs.standard-impl |
Distribution lib/apache-jstl |
org.apache.xalan 2.7.1 |
Try Eclipse Orbit |
|
org.apache.xml.serializer 2.7.1 |
Try Eclipse Orbit |
为了使用JSP你须要在OSGi容器中安装jetty-osgi-boot-jsp.jar。这个jar包能够在Maven仓库中心获取。它做为一个jetty-osgi-boot.jar延伸用于支持JSP。Jetty JSP OSGi容器能够支持全部webapp的JSTL tag库。若是你的web应用将不须要作任何修改便可使用。
然而,若是你想使用其余的taglibs,你要确保它们被安装到OSGi容器中,而且定义了系统属性或在webapp的MANIFEST头部声明。这是有必要的,由于OSGi容器的类加载模式与使用JSP容器很是不一样,webapp的MANIFEST文件若是没有包含足够的信息的话,那么OSGi环境将不容许JSP容器来获取和解析.jsp文件中的TLDs引用。
首先咱们看下面这个修改过的MANIFEST 例子,让你知道哪些是必需要的。这个例子使用了Spring servlet框架:
Bundle-SymbolicName: com.acme.sample
Bundle-Name: WebSample
Web-ContextPath: taglibs
Import-Bundle: org.springframework.web.servlet
Require-TldBundle: org.springframework.web.servlet
Bundle-Version: 1.0.0
Import-Package: org.eclipse.virgo.web.dm;version="[3.0.0,4.0.0)",org.s
pringframework.context.config;version="[2.5.6,4.0.0)",org.springframe
work.stereotype;version="[2.5.6,4.0.0)",org.springframework.web.bind.
annotation;version="[2.5.6,4.0.0)",org.springframework.web.context;ve
rsion="[2.5.6,4.0.0)",org.springframework.web.servlet;version="[2.5.6
,4.0.0)",org.springframework.web.servlet.view;version="[2.5.6,4.0.0)"
注解是Servlet 3.0和Servlet 3.1规范的重要组成部分。为了在OSGi Jetty容器中使用它们,你须要增长一些额外的依赖包到你的OSGi容器中:
Table 29.5. 注解的依赖包
Jar包 | 包名 | 路径 |
---|---|---|
org.ow2.asm:asm-5.0.1.jar |
org.objectweb.asm |
|
org.ow2.asm:asm-commons-5.0.1.jar |
org.objectweb.asm.commons |
|
org.ow2.asm:asm-tree-5.0.1.jar |
org.objectweb.asm.tree |
|
org.apache.aries:org.apache.aries.util-1.0.1.jar |
org.apache.aries.util |
|
org.apache.aries.spifly:org.apache.aries.spifly.dynamic.bundle-1.0.1.jar |
org.apache.aries.spifly.dynamic.bundle |
|
javax.annotation:javax.annotation-api-1.2.jar |
javax.annotation-api |
Maven central |
jta api version 1.1.1 (eg org.apache.geronimo.specs:geronimo-jta_1.1_spec-1.1.1.jar)* |
Maven central |
|
javax mail api version 1.4.1 (eg org.eclipse.jetty.orbit:javax.mail.glassfish-1.4.1.v201005082020.jar)* |
Maven central |
|
jetty-jndi |
org.eclipse.jetty.jndi |
Distribution lib/ |
jetty-plus |
org.eclipse.jetty.plus |
Distribution lib/ |
jetty-annotations |
org.eclipse.jetty.annotations |
Distribution lib/ |
若是你想使用注解,你须要部署这些依赖包。
你能够部署这些jar包的最后一个版本,然而特定的版本已知是正确的,通过测试并在OSGi环境下运行过。
即使你的web应用不使用注解,你也应该部署这些jar包,由于你的应用可能依赖Jetty的模块或者使用 javax.servlet.ServletContainerInitializer。这个接口须要注解的支持。
Weld能够用来增长servlet、Listeners 、Filters对CDI的支持。在Jetty9中配置是很是简单的。
一、启动时确保模块调用cdi
二、确保你的WEB-INF/web.xml包含以下内容:
<listener> <listener-class>org.jboss.weld.environment.servlet.Listener</listener-class> </listener> <resource-env-ref> <description>Object factory for the CDI Bean Manager</description> <resource-env-ref-name>BeanManager</resource-env-ref-name> <resource-env-ref-type>javax.enterprise.inject.spi.BeanManager</resource-env-ref-type> </resource-env-ref>
这当你启动Jetty的时候,你将看到如下输出:
2015-06-18 12:13:54.924:INFO::main: Logging initialized @485ms
2015-06-18 12:13:55.231:INFO:oejs.Server:main: jetty-9.3.1-SNAPSHOT
2015-06-18 12:13:55.264:INFO:oejdp.ScanningAppProvider:main: Deployment monitor [file:///tmp/cdi-demo/webapps/] at interval 1
2015-06-18 12:13:55.607:WARN:oeja.AnnotationConfiguration:main: ServletContainerInitializers: detected. Class hierarchy: empty
Jun 18, 2015 12:13:55 PM org.jboss.weld.environment.servlet.EnhancedListener onStartup
INFO: WELD-ENV-001008: Initialize Weld using ServletContainerInitializer
Jun 18, 2015 12:13:55 PM org.jboss.weld.bootstrap.WeldStartup <clinit>
INFO: WELD-000900: 2.2.9 (Final)
Jun 18, 2015 12:13:55 PM org.jboss.weld.environment.servlet.deployment.WebAppBeanArchiveScanner scan
WARN: WELD-ENV-001004: Found both WEB-INF/beans.xml and WEB-INF/classes/META-INF/beans.xml. It's not portable to use both locations at the same time. Weld is going to use file:/tmp/jetty-0.0.0.0-8080-cdi-webapp.war-_cdi-webapp-any-8161614308407422636.dir/webapp/WEB-INF/beans.xml.
Jun 18, 2015 12:13:55 PM org.jboss.weld.bootstrap.WeldStartup startContainer
INFO: WELD-000101: Transactional services not available. Injection of @Inject UserTransaction not available. Transactional observers will be invoked synchronously.
Jun 18, 2015 12:13:55 PM org.jboss.weld.interceptor.util.InterceptionTypeRegistry <clinit>
WARN: WELD-001700: Interceptor annotation class javax.ejb.PostActivate not found, interception based on it is not enabled
Jun 18, 2015 12:13:55 PM org.jboss.weld.interceptor.util.InterceptionTypeRegistry <clinit>
WARN: WELD-001700: Interceptor annotation class javax.ejb.PrePassivate not found, interception based on it is not enabled
Jun 18, 2015 12:13:56 PM org.jboss.weld.bootstrap.MissingDependenciesRegistry handleResourceLoadingException
Jun 18, 2015 12:13:56 PM org.jboss.weld.environment.servlet.WeldServletLifecycle findContainer
INFO: WELD-ENV-001002: Container detection skipped - custom container class loaded: org.jboss.weld.environment.jetty.JettyContainer.
Jun 18, 2015 12:13:56 PM org.jboss.weld.environment.jetty.JettyContainer initialize
INFO: WELD-ENV-001200: Jetty 7.2+ detected, CDI injection will be available in Servlets and Filters. Injection into Listeners should work on Jetty 9.1.1 and newer.
Jun 18, 2015 12:13:56 PM org.jboss.weld.environment.servlet.Listener contextInitialized
INFO: WELD-ENV-001006: org.jboss.weld.environment.servlet.EnhancedListener used for ServletContext notifications
Jun 18, 2015 12:13:56 PM org.jboss.weld.environment.servlet.EnhancedListener contextInitialized
INFO: WELD-ENV-001009: org.jboss.weld.environment.servlet.Listener used for ServletRequest and HttpSession notifications
2015-06-18 12:13:56.535:INFO:oejsh.ContextHandler:main: Started o.e.j.w.WebAppContext@6574b225{/cdi-webapp,file:///tmp/jetty-0.0.0.0-8080-cdi-webapp.war-_cdi-webapp-any-8161614308407422636.dir/webapp/,AVAILABLE}{/cdi-webapp.war}
2015-06-18 12:13:56.554:INFO:oejs.ServerConnector:main: Started ServerConnector@7112f81c{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
2015-06-18 12:13:56.587:INFO:oejus.SslContextFactory:main: x509={jetty.eclipse.org=jetty} wild={} alias=null for SslContextFactory@3214ee6(file:///tmp/cdi-demo/etc/keystore,file:///tmp/cdi-demo/etc/keystore)
2015-06-18 12:13:56.821:INFO:oejs.ServerConnector:main: Started ServerConnector@69176a9b{SSL,[ssl, http/1.1]}{0.0.0.0:8443}
2015-06-18 12:13:56.822:INFO:oejs.Server:main: Started @2383ms
Metro是Web Service的参考实现,你能够简单的使用Metro对你的web service和你的应用进行整合。步骤以下:
这就是全部的步骤,完成后便可把Jetty和Metro进行整合。下面是启动后的输出:
[2093] java -jar start.jar
2013-07-26 15:47:53.480:INFO:oejs.Server:main: jetty-9.0.4.v20130625
2013-07-26 15:47:53.549:INFO:oejdp.ScanningAppProvider:main: Deployment monitor [file:/home/user/jetty-distribution-9.3.11.v20160721/webapps/] at interval 1
Jul 26, 2013 3:47:53 PM com.sun.xml.ws.transport.http.servlet.WSServletContextListener contextInitialized
INFO: WSSERVLET12: JAX-WS context listener initializing
Jul 26, 2013 3:47:56 PM com.sun.xml.ws.server.MonitorBase createRoot
INFO: Metro monitoring rootname successfully set to: com.sun.metro:pp=/,type=WSEndpoint,name=/metro-async-AddNumbersService-AddNumbersImplPort
Jul 26, 2013 3:47:56 PM com.sun.xml.ws.transport.http.servlet.WSServletDelegate <init>
INFO: WSSERVLET14: JAX-WS servlet initializing
2013-07-26 15:47:56.800:INFO:oejsh.ContextHandler:main: Started o.e.j.w.WebAppContext@75707c77{/metro-async,file:/tmp/jetty-0.0.0.0-8080-metro-async.war-_metro-async-any-/webapp/,AVAILABLE}{/metro-async.war}
2013-07-26 15:47:56.853:INFO:oejs.ServerConnector:main: Started ServerConnector@47dce809{HTTP/1.1}{0.0.0.0:8080}
Ant Jetty插件是Jetty9 jetty-ant模块下的一部分。这个插件让经过Ant启动Jetty成为可能,而且将Jetty应用嵌入到你的构建过程当中。它提供绝大部分Maven一样的功能。须要添加如下依赖:
<dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-ant</artifactId> </dependency>
为了在Ant中运行你的Jetty,你须要Jetty安装包和jetty-ant的jar包。步骤以下:
如今你已经准备好编辑或建立 build.xml 文件了。
以一个空的build.xml文件中开始。
<project name="Jetty-Ant integration test" basedir="."> </project>
增长一个<taskdef>标签来引用全部可用的Jetty task
<project name="Jetty-Ant integration test" basedir="."> <path id="jetty.plugin.classpath"> <fileset dir="jetty-lib" includes="*.jar"/> </path> <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" /> </project>
接下来你要为运行的Jetty增长新的任务
<project name="Jetty-Ant integration test" basedir="."> <path id="jetty.plugin.classpath"> <fileset dir="jetty-lib" includes="*.jar"/> </path> <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" /> <target name="jetty.run"> <jetty.run /> </target> </project>
这是须要的最少的配置。如今你能够启动Jetty,默认端口为8080。
在命令行中输入如下内容:
> ant jetty.run
一系列的配置属性能够帮助你设置Jetty的运行环境,这样你的web应用将有你须要的全部资源:
端口和链接:
为了配置启动端口你须要定一个链接。首先你须要配置一个<typedef> 标签用于定义链接:
<project name="Jetty-Ant integration test" basedir="."> <path id="jetty.plugin.classpath"> <fileset dir="jetty-lib" includes="*.jar"/> </path> <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" /> <typedef name="connector" classname="org.eclipse.jetty.ant.types.Connector" classpathref="jetty.plugin.classpath" loaderref="jetty.loader" /> <target name="jetty.run"> <jetty.run> <connectors> <connector port="8090"/> </connectors> </jetty.run> </target> </project>
你能够将端口设置为0,那么Jetty启动时将自动分配一个可用的端口。你能够经过访问系统属性jetty.ant.server.port和jetty.ant.server.host得到启动的信息。
登陆服务
若是你的应用须要身份认证和登陆服务,你能够在Jetty容器中进行配置。这里有一个例子用来展现如何配置一个org.eclipse.jetty.security.HashLoginService服务:
<project name="Jetty-Ant integration test" basedir="."> <path id="jetty.plugin.classpath"> <fileset dir="jetty-lib" includes="*.jar"/> </path> <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" /> <typedef name="hashLoginService" classname="org.eclipse.jetty.security.HashLoginService" classpathref="jetty.plugin.classpath" loaderref="jetty.loader" /> <target name="jetty.run"> <jetty.run> <loginServices> <hashLoginService name="Test Realm" config="${basedir}/realm.properties"/> </loginServices> </jetty.run> </target> </project>
请求日志
requestLog 选项运行你为Jetty实例指定一个请求日志。你可使用org.eclipse.jetty.server.NCSARequestLog或者你本身的实现类:
<project name="Jetty-Ant integration test" basedir="."> <path id="jetty.plugin.classpath"> <fileset dir="jetty-lib" includes="*.jar"/> </path> <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" /> <target name="jetty.run"> <jetty.run requestLog="com.acme.MyFancyRequestLog"> </jetty.run> </target> </project>
临时目录
你能够把一个文件夹配置为临时目录用于存储文件,例如编译后的jsp,经过tempDirectory 选项进行配置:
<project name="Jetty-Ant integration test" basedir="."> <path id="jetty.plugin.classpath"> <fileset dir="jetty-lib" includes="*.jar"/> </path> <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" /> <target name="jetty.run"> <jetty.run tempDirectory="${basedir}/jetty-temp"> </jetty.run> </target> </project>
其余context 处理器
你有可能会在你程序运行的同时想要运行其余的context 处理器,你能够经过<contextHandlers>进行指定,在用以前首先定义一个<typedef>:
<project name="Jetty-Ant integration test" basedir="."> <path id="jetty.plugin.classpath"> <fileset dir="jetty-lib" includes="*.jar"/> </path> <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" /> <typedef name="contextHandlers" classname="org.eclipse.jetty.ant.types.ContextHandlers" classpathref="jetty.plugin.classpath" loaderref="jetty.loader" /> <target name="jetty.run"> <jetty.run> <contextHandlers> <contextHandler resourceBase="${basedir}/stuff" contextPath="/stuff"/> </contextHandlers> </jetty.run> </target> </project>
系统参数
为了方便你能够定义系统参数,经过使用 <systemProperties>元素:
<project name="Jetty-Ant integration test" basedir="."> <path id="jetty.plugin.classpath"> <fileset dir="jetty-lib" includes="*.jar"/> </path> <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" /> <target name="jetty.run"> <jetty.run> <systemProperties> <systemProperty name="foo" value="bar"/> </systemProperties> </jetty.run> </target> </project>
Jetty xml文件
若是你有大量的文件用来配置容器,你能够经过xml来配置管理:
<project name="Jetty-Ant integration test" basedir="."> <path id="jetty.plugin.classpath"> <fileset dir="jetty-lib" includes="*.jar"/> </path> <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" /> <target name="jetty.run"> <jetty.run jettyXml="${basedir}/jetty.xml"> </jetty.run> </target> </project>
扫描文件改动
最有用的模块是运行Jetty插件来自动扫描文件改动并重启服务。scanIntervalSeconds 选项控制扫描的频率,默认值为0不扫描,下面例子指定扫描为5秒:
<project name="Jetty-Ant integration test" basedir="."> <path id="jetty.plugin.classpath"> <fileset dir="jetty-lib" includes="*.jar"/> </path> <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" /> <target name="jetty.run"> <jetty.run scanIntervalSeconds="5"> </jetty.run> </target> </project>
中止运行
在普通模式中(daemon="false"),<jetty.run> 任务将会一直运行直到输入Ctrl+C,能够经过配置<jetty.stop>元素来配置中止端口:
<project name="Jetty-Ant integration test" basedir="."> <path id="jetty.plugin.classpath"> <fileset dir="jetty-lib" includes="*.jar"/> </path> <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" /> <target name="jetty.run"> <jetty.run stopPort="9999" stopKey="9999"> </jetty.run> </target> <target name="jetty.stop"> <jetty.stop stopPort="9999" stopKey="9999" stopWait="10"/> </target> </project>
为了在Ant中中止Jetty的运行,输入如下:
> ant jetty.stop
能够为org.eclipse.jetty.ant.AntWebAppContext类增长一个<typedef>标签,并配置一个webApp名字,而后增长一个<webApp>标签来描述你须要运行的web项目。下面的例子部署了一个web应用,应用在foo/文件夹下,context的Path为“/”:
<project name="Jetty-Ant integration test" basedir="."> <path id="jetty.plugin.classpath"> <fileset dir="jetty-lib" includes="*.jar"/> </path> <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" /> <typedef name="webApp" classname="org.eclipse.jetty.ant.AntWebAppContext" classpathref="jetty.plugin.classpath" loaderref="jetty.loader" /> <target name="jetty.run"> <jetty.run> <webApp war="${basedir}/foo" contextPath="/"/> </jetty.run> </target> </project>
不须要解压一个war文件,能够很方便的部署一个war文件:
<project name="Jetty-Ant integration test" basedir="."> <path id="jetty.plugin.classpath"> <fileset dir="jetty-lib" includes="*.jar"/> </path> <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" /> <typedef name="webApp" classname="org.eclipse.jetty.ant.AntWebAppContext" classpathref="jetty.plugin.classpath" loaderref="jetty.loader" /> <target name="jetty.run"> <jetty.run> <webApp war="${basedir}/foo.war" contextPath="/"/> </jetty.run> </target> </project>
你也能够同时部署多个web应用,配置以下:
<project name="Jetty-Ant integration test" basedir="."> <path id="jetty.plugin.classpath"> <fileset dir="jetty-lib" includes="*.jar"/> </path> <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" /> <typedef name="webApp" classname="org.eclipse.jetty.ant.AntWebAppContext" classpathref="jetty.plugin.classpath" loaderref="jetty.loader" /> <target name="jetty.run"> <jetty.run> <webApp war="${basedir}/foo.war" contextPath="/"/> <webApp war="${basedir}/other contextPath="/other"/> <webApp war="${basedir}/bar.war" contextPath="/bar"/> </jetty.run> </target> </project>
org.eclipse.jetty.ant.AntWebAppContext类是 org.eclipse.jetty.webapp.WebAppContext类的扩展,你能够经过增长同属性set方法来配置它(不须要设置或新增前缀)。
这里有一个例子,指定了web.xml(与AntWebAppContext.setDescriptor()功能相同)和web应用的临时目录(与 AntWebAppContext.setTempDirectory()功能相同)。
<project name="Jetty-Ant integration test" basedir="."> <path id="jetty.plugin.classpath"> <fileset dir="jetty-lib" includes="*.jar"/> </path> <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" /> <typedef name="webApp" classname="org.eclipse.jetty.ant.AntWebAppContext" classpathref="jetty.plugin.classpath" loaderref="jetty.loader" /> <target name="jetty.run"> <jetty.run> <webApp descriptor="${basedir}/web.xml" tempDirectory="${basedir}/my-temp" war="${basedir}/foo" contextPath="/"/> </jetty.run> </target> </project>
其余额外的对AntWebAppContext 配置以下;
额外的classes和jar包
若是你的web应用的classes和jar包不仅仅放在WEB-INF下面,你可使用<classes> 和 <jar>元素来配置它们,例子以下:
<project name="Jetty-Ant integration test" basedir="."> <path id="jetty.plugin.classpath"> <fileset dir="jetty-lib" includes="*.jar"/> </path> <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" /> <typedef name="webApp" classname="org.eclipse.jetty.ant.AntWebAppContext" classpathref="jetty.plugin.classpath" loaderref="jetty.loader" /> <target name="jetty.run"> <jetty.run> <webApp descriptor="${basedir}/web.xml" tempDirectory="${basedir}/my-temp" war="${basedir}/foo" contextPath="/"> <classes dir="${basedir}/classes"> <include name="**/*.class"/> <include name="**/*.properties"/> </classes> <lib dir="${basedir}/jars"> <include name="**/*.jar"/> <exclude name="**/*.dll"/> </lib> </webApp> </jetty.run> </target> </project>
context 属性
Jetty容许你对web应用的ServletContext 设置属性。你能够在context xml文件中进行配置,为了方便也能够在build文件中进行配置:
<project name="Jetty-Ant integration test" basedir="."> <path id="jetty.plugin.classpath"> <fileset dir="jetty-lib" includes="*.jar"/> </path> <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" /> <typedef name="webApp" classname="org.eclipse.jetty.ant.AntWebAppContext" classpathref="jetty.plugin.classpath" loaderref="jetty.loader" /> <target name="jetty.run"> <jetty.run> <webApp war="${basedir}/foo" contextPath="/"> <attributes> <attribute name="my.param" value="123"/> </attributes> </webApp> </jetty.run> </target> </project>
jetty-env.xml 文件
若是你使用一些新特性如JNDJ在你的应用程序中,你须要在WEB-INF/jetty-env.xml文件中配置资源。你须要使用jettyEnvXml属性来告诉Ant配置文件放在那里:
<project name="Jetty-Ant integration test" basedir="."> <path id="jetty.plugin.classpath"> <fileset dir="jetty-lib" includes="*.jar"/> </path> <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" /> <typedef name="webApp" classname="org.eclipse.jetty.ant.AntWebAppContext" classpathref="jetty.plugin.classpath" loaderref="jetty.loader" /> <target name="jetty.run"> <jetty.run> <webApp war="${basedir}/foo" contextPath="/" jettyEnvXml="${basedir}/jetty-env.xml"> <attributes> </webApp> </jetty.run> </target> </project>
context Xml文件
您可能会喜欢或甚至须要作一些高级配置您的Web应用程序之外的蚂蚁构建文件。在这种状况下,您可使用ant插件在部署以前应用到您的Web应用程序的标准上下文XML配置文件。要注意,上下文XML文件的设置覆盖了在生成文件中定义的属性和嵌套元素的设置。???
project name="Jetty-Ant integration test" basedir="."> <path id="jetty.plugin.classpath"> <fileset dir="jetty-lib" includes="*.jar"/> </path> <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" /> <typedef name="webApp" classname="org.eclipse.jetty.ant.AntWebAppContext" classpathref="jetty.plugin.classpath" loaderref="jetty.loader" /> <target name="jetty.run"> <jetty.run> <webApp war="${basedir}/foo" contextPath="/" contextXml="${basedir}/jetty-env.xml"> <attributes> </webApp> </jetty.run> </target> </project>
Jetty文档的目录详见:http://www.cnblogs.com/yiwangzhibujian/p/5832294.html
Jetty第一章翻译详见:http://www.cnblogs.com/yiwangzhibujian/p/5832597.html
Jetty第四章(21-22)详见:http://www.cnblogs.com/yiwangzhibujian/p/5845623.html
Jetty第四章(23)详见:http://www.cnblogs.com/yiwangzhibujian/p/5856857.html
Jetty第四章(24-27)详见:http://www.cnblogs.com/yiwangzhibujian/p/5858544.html
此次翻译的是第四部分的第28到30小节。翻译的过程当中以为Jetty的参考文档写的并非特别好,偏向于理论,并且还有不少地方等待完善,虽然也有不少示例代码,可是都是一些代码片断,不太适合入门使用,因此我也打算在翻译快结束的时候,根据本身的理解写几篇用于实践的项目例子。