Jetty使用教程(四:28-30)—Jetty開發指南

二十8、延續機制支持

28.1 延續簡介

  延續是一種機制用來實現相似於Servlet 3.0異步功能的異步Servlet,但提供了一個簡單易操做的接口。php

28.1.1 爲何使用異步Servlets 

不使用異步IO:css

  異步servlet的概念每每與異步IO或NIO的使用產生混淆。可是異步Servlets 和異步IO仍是有主要不一樣點:html

    • HTTP請求一般很小而且位於一個單獨的包,Servlets 不多在請求時阻塞。
    • 許多responses 一般很小而且大小適合server緩衝,因此servlets 一般不會再寫入response時堵塞
    • 即使咱們能在servlet中使用異步IO,那也將時編程變得更加困難。例如當一個應用程序讀到2到3個字節的UTF-8它會怎麼作?它不得不緩衝等待更多的字節。這件事最好由容器來作而不是應用程序。

異步等待:java

  異步servlets 的主要用法是用來等待非IO的事件或資源。許多web應用程序須要等待處理HTTP請求的各類階段,例如:web

    • 處理請求前等待資源可用(例如:thread、JDBC鏈接)
    • 在AJAX Comet應用中等待一個應用程序的事件(例如:聊天消息、價格變更)
    • 等待遠程服務的一個響應(例如:RESTful 、SOAP )

  servlet API(2.5以前)僅支持一種同步調用方式,因此servlet 的任何等待都是阻塞式的。不幸的是,這意味着分配給請求的線程將會在等待全部資源的時候被持有:內核線程、棧存儲、緩衝池、字符轉換器、EE認真context等等。保存對這些資源的等待會浪費大量的系統資源。若是等待是異步進行的,那麼能夠進行更好的擴展和提升服務質量。redis

28.1.2 異步Servlets 例子

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過濾器的質量。

28.1.3 Servlet線程模型

  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,使用一個新的線程,一個響應就會產生。

28.2 使用延續

  異步servlet最初是由Jetty的延續機制引進來的,這是Jetty的一個特殊的機制。Jetty7之後,延續的API已經被擴展成一個通用的API,將在任何servlet-3.0容器上進行異步工做,Jetty6,7,8一樣支持。延續機制還能夠在servlet 2.5 容器下以阻塞方式運行。

28.2.1 得到一個延續

   ContinuationSupport工廠能夠經過request來得到一個延續實例:

Continuation continuation = ContinuationSupport.getContinuation(request);

28.2.2 掛起一個請求

  爲了掛起一個請求,掛起方法能夠在延續上被調用:

    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異常。

28.2.3 恢復一個請求

  一旦異步事件發生,那麼延續將被恢復:

    void myAsyncCallback(Object results)
    {
    continuation.setAttribute("results",results);
    continuation.resume();
    }

  當延續被恢復,請求會被從新分配到這個servlet 容器,就像請求從新來過同樣。然而在從新分配過程當中, continuation.isInitial()方法返回false,全部設置到異步處理器上的參數都是有效的。

  延續的恢復相似於Servlet 3.0 的 AsyncContext.dispatch()方法。

28.2.4 完成一個請求

  當從新恢復一個請求時,異步處理器可能會生成響應,在寫入響應後,處理器必須顯示的經過調用方法代表延續完成了。

    void myAsyncCallback(Object results)
    {
      writeResults(continuation.getServletResponse(),results);
      continuation.complete();
    }

  當完成方法被調用,容器會安排響應提交併刷新。延續的完成相似於Servlet 3.0 的 AsyncContext.complete()。

28.2.5 延續的監聽

  一個應用有可能經過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。

28.3 經常使用延續模式

28.3.1 暫停恢復模式

  暫停/恢復模式用在當一個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框架)或者一個事件將恢復多個請求致使容器的線程池被多個處理器使用。

28.3.2 暫停繼續模式

  暫停/完成模式是用來當一個異步處理器被用來生成一個響應的狀況:

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。

28.3.3 示例

//
//  ========================================================================
//  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);
    }
    
}
View Code

  這個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
        {
        }
    }
}
View Code

  這個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);
        }
    }
}
View Code

  這個ProxyServlet(這是jetty-proxy包中的一個類的)例子使用掛起/完成模式,Jetty異步客戶端實現一個可擴展的代理服務器。

二十9、框架

29.1 Spring設置

  你能夠在代碼中組裝和配置Jetty或者徹底使用Spring的IOC框架。若是你想作的僅僅是將你的Jetty服務嵌入到Spring中,那麼能夠簡單的看下面例子的xml片斷。若是你想使用spring來替換jetty-xml 用於啓動一個普通的Jetty應用,你能夠這麼作可是將不會依賴其餘的系統框架。

29.1.1 Jetty-Spring模塊

  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。

29.1.2 使用Spring配置Jetty

  經過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>

29.2 OSGI

29.2.1 簡介

  Jetty OSGi基礎設施提供了一個內置有OSGi容器的Jetty容器內。傳統的JavaEE web應用能夠被部署,除此以外還有Jetty的與OSGi一塊兒的ContextHandlers。此外,基礎設施還支持OSGi HttpService接口。

29.2.2 常規設置

  全部的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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

+提示

    咱們建議在部署的時候同時添加annotation-related jar包,由於愈來愈多的Servlet使用註解來完成它們的功能。

  你也一樣會須要OSGi事件管理服務OSGi配置管理服務。若是你的OSGi容器沒有自動將這些服務啓用,那麼你須要以適當的方式將它們添加到容器中。

29.2.3 Jetty OSGi容器

29.2.3.1 jetty-osgi-boot jar

  如今你已經安裝好Jetty最基本的jar包,那麼你須要繼續安裝jetty-osgi-boot.jar包,在Maven倉庫中下載,點擊我(http://central.maven.org/maven2/org/eclipse/jetty/osgi/jetty-osgi-boot/)。

  當它啓動時,這個包將會實例化並在Jetty的OSGi容器可用。若是這個包沒有自動安裝到OSGi容器中,那麼你應該使用命令手動將其安裝到容器的中。

29.2.3.2 定製Jetty容器

  在安裝以前,你可能但願定製一下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

29.2.3.3 將Jetty容器作爲OSGi一個服務

  如今你能夠將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列表

29.2.3.4 新增更多的Jetty服務

  正如咱們在前面章節中所看到的,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);
    }
}

29.2.4 做爲Webapps部署Bundles 

29.2.4.1 部署規則

  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中。

29.2.4.2 爲webapp bundle定義上下文路徑

  正如咱們所看到的在前面的小節中,若是一個bundled的MANIFEST 文件包含RFC-66規範的頭屬性Web-ContextPath,那麼Jetty將會使用這個作爲上下文路徑。若是MANIFEST 文件中沒有這個頭信息,那麼Jetty將會根據最後一個bundle元素的地址(經過Bundle.getLocation()方法,並去掉擴展名)編造一個上下文路徑。

  例如,咱們有一個bundle,路徑以下:

file://some/where/over/the/rainbow/oz.war

  那麼生成的上下文路徑將是: 

/oz

29.2.4.3 用於webapp bundles的額外屬性

  你能夠經過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文件的地址。

29.2.5 部署Bundles作爲JettyContextHandlers

29.2.5.1 基本部署

  爲了部署一個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"。

29.2.5.2 爲ContextHandler Bundle肯定上下文路徑

  一般ContextHandler的上下文路徑在context的xml文件中進行配置。然而你仍然能夠經過使用MANIFEST文件中的Web-ContextPath屬性進行覆蓋。

29.2.5.3 額外的context xml文件可用屬性

  在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>

29.2.6 部署服務作爲webapp

  爲了部署而去監聽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進行額外的配置:

  • Jetty-WarFolderPath (9.3及之後版本) 或者 Jetty-WarResourcePath::webapp靜態資源root根目錄。
  • Web-ContextPath::部署webapp的路徑。
  • Jetty-defaultWebXmlFilePath::部署webapp包的webdefault.xml文件的路徑。默認在OSGi容器內。
  • Jetty-WebXmlFilePath::web.xml的路徑,默認爲WEB-INF/web.xml 。
  • Jetty-extraClassPath::增長到webapp當前的類加載的類路徑。
  • Jetty-bundleInstall::用於覆蓋安裝包的基文件夾 - 經常使用於沒有打包的OSGi框架。
  • Require-TldBundle::額外的包含TLD的包。
  • managedServerName::部署webapp bundle的服務實例名。若是沒有指定,那麼默認爲 "defaultJettyServer"。
  • Jetty-WarFragmentResourcePath::包含webapp靜態資源的在web-bundle中的路徑。這些路徑將會追加到webapp的基礎資源路徑下。
  • Jetty-WarPrependFragmentResourcePath::將要部署到webapp中的bundle路徑列表。或者可能包含一個單獨的Jetty context文件叫作"jetty-webapp-context.xml",在webapp 的bundle的META-INF文件夾下,它將會自動部署到webapp中。

29.2.7 部署服務作爲JettyContextHandlers

  同部署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文件中。

29.2.8 對於OSGi服務平臺的企業規範支持

  Jetty OSGi容器對於 WebAppContexts 和 ContextHandlers 實現企業規範v4.2,在前面的章節已介紹經過包或者OSGi服務的形式進行部署。

Context屬性

  對於每個WebAppContext或ContextHandler,下面的屬性是必須的:

    • osgi-bundleContext此屬性的值是表明與WebAppContext 或ContextHandler關聯的表明包的BundleContext。

Service 屬性

  規範要求,每個被Jetty OSGi容器部署的WebAppContext或ContextHandler,同時必須發佈爲OSGi服務(除非它已是一個OSGi服務),下面的屬性與服務有關:

    • osgi.web.symbolicname與WebAppContext 或ContextHandler關聯的包的符號名稱
    • osgi.web.version與WebAppContext 或ContextHandler關聯的包的版本
    • osgi.web.contextpathWebAppContext 或ContextHandler的context路徑

OSGi 事件

  根據規範要求,下面的事件須要被公佈:

    • org/osgi/service/web/DEPLOYINGJetty OSGi容器部署一個WebAppContext或者ContextHandler
    • org/osgi/service/web/DEPLOYED已經完成部署併成爲一個service
    • org/osgi/service/web/UNDEPLOYINGJetty OSGi容器卸載一個WebAppContext或者ContextHandler
    • org/osgi/service/web/UNDEPLOYEDJetty OSGi容器卸載完成一個WebAppContext或者ContextHandler,已再也不是一個service
    • org/osgi/service/web/FAILEDJetty OSGi容器部署一個WebAppContext或者ContextHandler失敗

29.2.9 JSP的使用

29.2.9.1 安裝

  爲了在你的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

29.2.9.2 jetty-osgi-boot-jsp包

  爲了使用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)"

29.2.10 使用Annotations/ServletContainerInitializers

  註解是Servlet 3.0和Servlet 3.1規範的重要組成部分。爲了在OSGi Jetty容器中使用它們,你須要增長一些額外的依賴包到你的OSGi容器中:

  Table 29.5. 註解的依賴包

Jar包 包名 路徑

org.ow2.asm:asm-5.0.1.jar

org.objectweb.asm

Maven central

org.ow2.asm:asm-commons-5.0.1.jar

org.objectweb.asm.commons

Maven central

org.ow2.asm:asm-tree-5.0.1.jar

org.objectweb.asm.tree

Maven central

org.apache.aries:org.apache.aries.util-1.0.1.jar

org.apache.aries.util

Maven central

org.apache.aries.spifly:org.apache.aries.spifly.dynamic.bundle-1.0.1.jar

org.apache.aries.spifly.dynamic.bundle

Maven central

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。這個接口須要註解的支持。

29.3 Weld

  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

29.4 Metro

  Metro是Web Service的參考實現,你能夠簡單的使用Metro對你的web service和你的應用進行整合。步驟以下:

  1. 下載Metro 安裝包,解壓到硬盤,使用$metro.home來代替解壓路徑。
  2. 建立$jetty.home/lib/metro文件夾。
  3. 將$metro.home/lib下的jar包拷貝到$jetty.home/lib/metro下面。
  4. 編輯start.ini文件,在結尾處增長 OPTIONS=metro。

  這就是全部的步驟,完成後便可把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}

三10、Ant 和 Jetty

  Ant Jetty插件是Jetty9 jetty-ant模塊下的一部分。這個插件讓經過Ant啓動Jetty成爲可能,而且將Jetty應用嵌入到你的構建過程當中。它提供絕大部分Maven一樣的功能。須要添加如下依賴:

<dependency>
   <groupId>org.eclipse.jetty</groupId>
   <artifactId>jetty-ant</artifactId>
 </dependency>

30.1 項目前期準備

  爲了在Ant中運行你的Jetty,你須要Jetty安裝包和jetty-ant的jar包。步驟以下:

  1. 下載Jetty安裝包,並解壓到本地。
  2. 得到the jetty-ant Jar。
  3. 在你的項目中新建jetty-lib/文件夾。
  4. 拷貝Jetty全部的jar包到jetty-lib文件夾下,拷貝時注意去掉層級關係,全部的jar包都要在jetty-lib路徑下。
  5. 拷貝 jetty-ant.jar到jetty-lib文件夾下。
  6. 在項目中新建jetty-temp文件夾。

  如今你已經準備好編輯或建立 build.xml 文件了。

30.2 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。

30.3 經過Ant啓動Jetty

  在命令行中輸入如下內容:

> ant jetty.run

30.4 配置Jetty容器

  一系列的配置屬性能夠幫助你設置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

30.5 部署一個web應用

  能夠爲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>

30.5.1 部署一個war文件

  不須要解壓一個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>

30.5.2 部署多個web應用

  你也能夠同時部署多個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>

30.6 配置web應用

  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的參考文檔寫的並非特別好,偏向於理論,並且還有不少地方等待完善,雖然也有不少示例代碼,可是都是一些代碼片斷,不太適合入門使用,因此我也打算在翻譯快結束的時候,根據本身的理解寫幾篇用於實踐的項目例子。

相關文章
相關標籤/搜索