2 嵌入式jetty的HTTP實現
2.1 簡單HTTP實現
2.1.1 HTTP SERVER端實現
2.1.1.1 HTTP SERVER端實現概述
在代碼中嵌入一個Jetty server,通常需要以下步驟:
l 創建Server
l 添加/配置Connectors
l 添加/配置Handlers
l 添加/配置Servlets/Webapps到Handlers
l 啟動服務器並join
2.1.1.2 創建一個Server
Jetty的Service對象就是Jetty容器,實例化出這樣一個對象就產生了一個容器。
Server server = new Server();
可以為Server設置線程池,如果不設置Server處理請求默認為阻塞狀態.
server.setThreadPool(newExecutorThreadPool(25, 50, 30000));
簡單示例如下:
public class SimplestServer
{
public static void main(String[] args) throwsException
{
Serverserver = new Server(8080);
server.start();
server.join();
}
}
2.1.1.3 配置Connectors
創建Server實例時傳入了一個端口號參數,程序內部會創建一個Connector的默認實例,在指定的端口上監聽請求。然而,通常嵌入式的Jetty需要為一個Server實例顯式地實例化並配置一個或多個Connector。
//實例化
SelectChannelConnector connector = newSelectChannelConnector();
//設置主機地址
connector.setHost("localhost");
//設置端口號
connector.setPort(40404);
/*設置最大空閑時間,最大空閑時間在以下幾種情況下應用:
1) 等待一個連接的新的請求時;
2) 讀取請求的頭信息和內容時;
3) 寫響應的頭信息和內容時;*/
connector.setMaxIdleTime(30000);
/*為connector設置線程池,如果不設置connector處理請求默認為阻塞狀態。如上所示Sever中也有個setThreadPool方法,我覺得如果通過connector進行設置后,會覆蓋Sever中的設置。
ExecutorThreadPool構造方法的三個參數分別為:
1) corePoolSize:線程池的基本大小也就是線程池的目標大小,即在沒有任務執行時線程池的大小( 線程池維護線程的最少數量);
2) maximunPoolSize:線程池最大大小,表示可同時活動的線程數量的上限;
3) keepAliveTime:存活時間,單位為毫秒,如果某個線程的空閑時間超過了存活時間,那么將被標記為可回收的;
*/
connector.setThreadPool(new ExecutorThreadPool(25, 50,30000));
/*設置這個Server的連接(可以是多個),每個連接有對應的線程池和Handler*/
server.setConnectors(newConnector[] { connector });
嵌入式Jetty服務器的實現的步驟通常為:
2.1.1.4 編寫Handlers
為了對每個請求作出相應,Jetty要求為Server設定Handler,Handler可以處理:
1) 檢查或修改Http請求
2) 生成完整的Http響應
3) 調用另一個Handler
4) 選擇一個或多個Handlers調用
下述代碼顯示了一個簡單HelloHandler的實現:
public class HelloHandler extends AbstractHandler
{
public void handle(String target,Request baseRequest,HttpServletRequest request,HttpServletResponse response)
throwsIOException, ServletException
{
response.setContentType("text/html;charset=utf-8");
response.setStatus(HttpServletResponse.SC_OK);
baseRequest.setHandled(true);
response.getWriter().println("<h1>Hello World</h1>");
}
}
handle為AbstractHandler抽象類中定義的抽象方法,我們對請求的處理操作主要通過此方法來完成,handle中參數包括:
target:請求的目標,可以是一個URI或者一個命名dispatcher中的名字。
baseRequest:Jetty的可變請求對象,總是unwrapped。
Request:不變的請求對象,可能會被過濾器或servlet包裝
Response:響應,可能會被過濾器或servlet包裝
hadler設定響應的狀態,內容類型,並在使用writer生成響應體之前標記請求為已處理。
下面是運行Jetty server 使用HelloWorld Handler的例子:
publicstaticvoid main(String[]args)throwsException
{
Server server=new Server(8080);
server.setHandler(new HelloHandler());
server.start();
server.join();
}
2.1.1.5 啟動Server並join
server.start();
server.join();
至此,嵌入式jetty的Server端基本編寫方式已經完成,然而復雜的請求是建立在很多Handlers的基礎上的。下面將詳細介紹Handlers的其他內容。
2.1.1.6 理解Handler的幾個重要概念
復雜的請求處理通常是建立在多個Handler以不同的方式進行組合上的。
Handler Collection: 維護一個handlers的集合,順序調用每一個handler。
Handler List:維護一個handlers的集合,依次執行每個handler直到發生異常,或者響應被提交,或者request.isHandled()返回true。
Handler Wrapper:handler的基類,在面向方面編程中能夠很好地把一系列處理操作聯系在一起。例如,一個標准的Web應用程序是由一連串的context,session,security和servlet處理程序進行執行的。
Context Handler Collection:ContextHandler Collection利用URI最長的前綴來選擇特定的context,即擁有相同的URI最長的前綴的context屬於同一個ContextHandler Collection。
2.1.1.7 配置文件服務器
下面的代碼使用HandlerList來組合ResourceHandler 和DefaultHandler,其中ResourceHandler可以用於處理當前目錄下的靜態內容:
public class FileServer
{
public static void main(String[] args) throws Exception
{
Server server = new Server();
SelectChannelConnector connector = new SelectChannelConnector();
connector.setPort(8080);
server.addConnector(connector);
ResourceHandler resource_handler = new ResourceHandler();
resource_handler.setDirectoriesListed(true);
resource_handler.setWelcomeFiles(new String[]{ "index.html" });
resource_handler.setResourceBase(".");
HandlerList handlers = new HandlerList();
handlers.setHandlers(new Handler[] { resource_handler, new DefaultHandler() });
server.setHandler(handlers);
server.start();
server.join();
}
}
HandlerList包括ResourceHandler和DefaultHandler,后者主要用於當請求不匹配靜態資源時返回404錯誤頁面,在本例中即如果沒發現文件index.html,則請求轉向DefaultHandler,返回404錯誤頁面。
2.1.1.8 設置Contexts
ContextHandler 是一個HandlerWrapper,它只響應特定URL的請求。請求不匹配則不予以處理。請求匹配則執行對應的處理程序。
下面是一個使用例子:
public class OneContext
{
public static void main(String[] args) throws Exception
{
Server server = new Server(8080);
ContextHandler context = new ContextHandler();
context.setContextPath("/hello");
context.setResourceBase(".");
context.setClassLoader(Thread.currentThread().getContextClassLoader());
server.setHandler(context);
context.setHandler(new HelloHandler());
server.start();
server.join();
}
}
2.1.1.9 創建 Servlets
Servlets是提供處理HTTP請求的應用邏輯的的標准方式,Servlets強制將特定的URI映射到特定的servlet。下面是HelloServlet的示例代碼:
public class HelloServlet extends HttpServlet
{
private String greeting="Hello World";
public HelloServlet(){}
public HelloServlet(String greeting)
{
this.greeting=greeting;
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
response.setContentType("text/html");
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().println("<h1>"+greeting+"</h1>");
response.getWriter().println("session=" + request.getSession(true).getId());
}
}
2.1.1.10 設置ServletContext
ServletContextHandler是一種支持標准servlets的特殊的ContextHandler。
下面的代碼實現了通過ServletContextHandler注冊的三個helloworld servlet的實例:
public class OneServletContext
{
public static void main(String[] args) throws Exception
{
Server server = new Server(8080);
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/");
server.setHandler(context);
context.addServlet(new ServletHolder(new HelloServlet()),"/*");
context.addServlet(new ServletHolder(new HelloServlet("Buongiorno Mondo")),"/it/*");
context.addServlet(new ServletHolder(new HelloServlet("Bonjour le Monde")),"/fr/*");
server.start();
server.join();
}
}
2.1.1.11 設置Web Application Context
Web Applications context是ServletContextHandler的一個變化,它使用標准布局和web.xml來配置servlets ,filters等。
示例如下:
public class OneWebApp
{
public static void main(String[] args) throws Exception
{
String jetty_home = System.getProperty("jetty.home","..");
Server server = new Server(8080);
WebAppContext webapp = new WebAppContext();
webapp.setContextPath("/");
webapp.setWar(jetty_home+"/webapps/test.war");
server.setHandler(webapp);
server.start();
server.join();
}
}
在開發過程中如果你還沒有把你的web應用變成WAR文件,那么你可以這樣實現:
public classOneWebAppUnassembled
{
public static void main(String[] args) throws Exception
{
Server server = new Server(8080);
WebAppContext context = new WebAppContext();
context.setDescriptor(webapp+"/WEB-INF/web.xml");
context.setResourceBase("../test-jetty-webapp/src/main/webapp");
context.setContextPath("/");
context.setParentLoaderPriority(true);
server.setHandler(context);
server.start();
server.join();
}
}
2.1.1.12 配置Context Handler Collection
Context Handler Collection利用URI最長的前綴來選擇特定的context,即擁有相同的URI最長的前綴的context屬於同一個Context Handler Collection。
示例如下:
public class ManyContexts
{
public static void main(String[] args) throws Exception
{
Serverserver = new Server(8080);
ServletContextHandler context0 = newServletContextHandler(ServletContextHandler.SESSIONS);
context0.setContextPath("/ctx0");
context0.addServlet(new ServletHolder(newHelloServlet()),"/*");
context0.addServlet(new ServletHolder(newHelloServlet("Buongiorno Mondo")),"/it/*");
context0.addServlet(new ServletHolder(newHelloServlet("Bonjour le Monde")),"/fr/*");
WebAppContextwebapp = new WebAppContext();
webapp.setContextPath("/ctx1");
webapp.setWar(jetty_home+"/webapps/test.war");
ContextHandlerCollection contexts = newContextHandlerCollection();
contexts.setHandlers(new Handler[] { context0, webapp });
server.setHandler(contexts);
server.start();
server.join();
}
}
2.1.2 HTTP CLIENT端實現
2.1.2.1 HttpClient設置
在和HTTP服務器通信前,需要先設置HttpClient,然后啟動它。
HttpClient client = new HttpClient();
client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
client.start();
還可以為HttpClient添加其他的設置:
HttpClient client = newHttpClient();
client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
// 設置客戶端連接每個服務器地址的的最大連接數
client.setMaxConnectionsPerAddress(200);
// 每個client最多起250個線程
client.setThreadPool(newQueuedThreadPool(250));
// 設置超時時間,如果這么長時間服務器都沒有響應的話,則請求失敗。
client.setTimeout(30000);
client.start();
一定要在HttpClient啟動之前配置相關參數,否則配置無效。
正因為HttpClient沒有關於HTTP服務器特定地址的設置,所以它可以同時連接多個服務器。
2.1.2.2異步請求
一旦HttpClient被設置以后,使用HttpClient.send(HttpExchange exchange)方法,客戶端和服務器間的通信就被發起了。
exchange 必須設置;兩個重要的參數:要連接的地址和請求的URI。示例如下:
HttpExchange exchange = newHttpExchange();
// Optionally set the HTTP method
exchange.setMethod("POST");
exchange.setAddress(newAddress("ping.host.com", 80));
exchange.setURI("/ping");
// Or, equivalently, this:
exchange.setURL("http://ping.host.com/ping");
client.send(exchange);
System.out.println("Exchangesent");
client.send(exchange)在把exchange分配到線程池執行后會立刻返回,所以System.out方法在client.send(exchange)執行后會立刻執行。所以exchange既有可能在System.out之后執行也有可能在System.out之前執行。
2.1.2.3 請求過程控制
HttpExchange提供了以下可被重寫的回調函數來判斷一個exchange所處的狀態:
- onRequestCommitted(), 當請求行和請求頭被發送到HTTP服務器后調用。
- onRequestComplete(),當請求內容被發送到HTTP服務器后調用。
- onResponseStatus(Buffer httpVersion, int statusCode, Buffer statusMessage), 當響應行被處理時調用; 三個參數分別表示HTTP版本號(e.g. "HTTP/1.1"),服務器響應狀態碼(e.g. 200),服務器響應狀態信息(e.g. "OK")。
- onResponseHeader(Buffer name, Buffer value), 在每個響應頭信息被處理時調用; 兩個參數分別表示每個息的名字(e.g. "Content-Length")和值(e.g. "16384")。
- onResponseHeaderComplete(), 當所有的響應頭信息被處理完后調用。
- onResponseContent(Buffer content), 收到每個響應內容塊后都被調用。參數表示對應響應內容塊的內容。
- onResponseComplete(), 在響應內容被完全收到后調用。
下面是非正常條件下的回調函數:
- onConnectionFailed(Throwable x), 當嘗試與服務器進行連接失敗時調用。參數表示嘗試連接失敗后的異常信息。
- onException(Throwable x), 當與服務器連接成功但后來發生錯誤時調用。參數表示產生的異常信息。
- onExpire(), 服務器響應超時時調用。
- onRetry(), 當exchange 被重新發送時調用。
通常,我們通過重寫onResponseComplete()函數來實現服務器響應信息的獲取(比如響應頭和響應體)和執行其他的操作:
ContentExchange exchange = newContentExchange(true)
{
protected void onResponseComplete() throwsIOException
{
int status = getResponseStatus();
if (status == 200)
doSomething();
else
handleError();
}
};
下面給出一個進行過程控制的異步請求的例子:
packagetest.jetty;
importjava.io.IOException;
importorg.eclipse.jetty.client.Address;
importorg.eclipse.jetty.client.HttpClient;
importorg.eclipse.jetty.client.HttpExchange;
importorg.eclipse.jetty.io.Buffer;
importorg.eclipse.jetty.util.thread.QueuedThreadPool;
public class TestClient{
public static void main(String[] args) throwsException {
HttpClient client = new HttpClient();
client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
client.setThreadPool(new QueuedThreadPool(50));
client.setMaxConnectionsPerAddress(10);
client.setTimeout(5000);
//啟動之前先配置好
client.start();
HttpExchange exchange = new HttpExchange(){
@Override
public void onConnectionFailed(Throwable arg0) {
// TODO Auto-generated method stub
System.err.println("failed to connect webserver");
}
@Override
public void onException(Throwable arg0) {
// TODO Auto-generated method stub
System.err.println("unknown error");
}
@Override
public void onExpire() {
// TODO Auto-generated method stub
System.err.println("timeout");
}
@Override
public void onRequestCommitted() throws IOException{
// TODO Auto-generated method stub
System.out.println("request commited");
}
@Override
public void onRequestComplete() throws IOException{
// TODO Auto-generated method stub
System.out.println("request complete");
}
@Override
public void onResponseComplete() throws IOException{
// TODO Auto-generated method stub
System.out.println("responsecomplete");
}
@Override
public void onResponseContent(Buffer arg0) throwsIOException {
// TODO Auto-generated method stub
System.out.println("response content");
System.out.println(arg0.toString());
}
@Override
public void onResponseHeader(Buffer arg0, Buffer arg1)throws IOException {
// TODO Auto-generated method stub
System.out.println("response header");
System.out.println(arg0.toString() + " " +arg1.toString());
}
@Override
public void onResponseStatus(Buffer arg0, int arg1,Buffer arg2)
throwsIOException {
// TODO Auto-generated method stub
System.out.println("response status");
System.out.println(arg0.toString() + " " +arg1 + " " + arg2.toString());
}
@Override
public void onRetry() {
// TODO Auto-generated method stub
System.out.println("retry request");
}
@Override
public void onResponseHeaderComplete() throwsIOException {
// TODO Auto-generated method stub
System.out.println("response headercomplete");
}
};
exchange.setMethod("GET");
exchange.setAddress(newAddress("www.baidu.com",80));
exchange.setRequestURI("/");
//client.send會立即返回,exchange會被分發給線程池去處理,
client.send(exchange);
System.out.println("send");
}
}
2.1.2.4 同步請求
異步請求在性能上表現的最好,然而有時候我們需要使用同步請求來實現自己的需求,我們可以使用HttpExchange.waitForDone()方法來實現同步請求。
HttpClient client = new HttpClient();
client.start();
ContentExchange exchange = newContentExchange(true);
exchange.setURL("http://foobar.com/baz");
client.send(exchange);
// Waits until the exchange is terminated
int exchangeState = exchange.waitForDone();
if (exchangeState == HttpExchange.STATUS_COMPLETED)
doSomething();
else if (exchangeState ==HttpExchange.STATUS_EXCEPTED)
handleError();
else if (exchangeState ==HttpExchange.STATUS_EXPIRED)
handleSlowServer();
waitForDone()方法會一直處於等待狀態,直到exchange成功執行完,或者發生錯誤,或者響應超時。
2.2 HTTP認證實現
2.2.1HTTP對內認證的實現
2.2.1.1 對內認證需求
在http實現的過程中,有些url只用於程序內部的httpClient訪問,這些url不需要或者說不能對外開放,這就需要程序內部得有這樣一個機制,對於某些特定的url只允許程序內部訪問,不允許在程序外部進行訪問。
2.2.1.2 對內認證實現方式
為實現上述需求,我覺得一個很好的解決辦法是,對於程序內部的httpClient,在發送請求的時候,可以在頭信息中增加一個自定義的頭信息,該信息不對外開放,比如:名字為check,值為123456(該值只有程序內部知道),HTTP服務端在收到頭信息時要驗證這一項,如果有名字為check的頭信息,並且值和我們內部約定的一樣,則執行相應操作,否則不執行。
下面是一個實現的demo:
Server端:
package test;
importjava.io.FileNotFoundException;
importorg.eclipse.jetty.server.Connector;
importorg.eclipse.jetty.server.Handler;
importorg.eclipse.jetty.server.Server;
importorg.eclipse.jetty.server.handler.ContextHandler;
importorg.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.DefaultHandler;
importorg.eclipse.jetty.server.handler.HandlerCollection;
importorg.eclipse.jetty.server.nio.SelectChannelConnector;
public classSServer {
private Server server = null;
public static final String HOST ="192.168.0.158";
public static final int POST = 40404;
public static void main(String[] args)throws FileNotFoundException{
new SServer().startHttpServer();
}
public void startHttpServer() throwsFileNotFoundException {
server = new Server();
SelectChannelConnector connector= new SelectChannelConnector();
connector.setHost(HOST);
connector.setPort(POST);
server.setConnectors(newConnector[] { connector });
ContextHandler test = newContextHandler();
test.setContextPath("/test");
Handler handler = newHHandler();
test.setHandler(handler);
ContextHandlerCollectioncontexts = new ContextHandlerCollection();
contexts.setHandlers(newHandler[] { test });
HandlerCollection handlers = newHandlerCollection();
handlers.setHandlers(newHandler[] { contexts, new DefaultHandler() });
server.setHandler(handlers);
start();
}
private void start() {
try {
server.start();
server.join();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Handle類:
package test;
importjava.io.IOException;
importjavax.servlet.ServletException;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
importorg.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
public classHHandler extends AbstractHandler {
public HHandler() {
}
@Override
public void handle(final String target,final Request request, final HttpServletRequest servlet_request,
final HttpServletResponse response)throws IOException, ServletException {
if(request.getHeader("check")!=null&&request.getHeader("check").equals("123456")){
System.out.println(request.getHeader("check"));
response.setContentType("text/html;charset=utf-8");
response.setStatus(HttpServletResponse.SC_OK);
request.setHandled(true);
response.getWriter().println("<h1>HelloWorld</h1>");
}
}
}
Client端:
package test;
importorg.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpExchange;
importorg.eclipse.jetty.util.thread.QueuedThreadPool;
public classCClient {
/**
*@param args
*/
public static void main(String[] args)throwsException {
HttpClient client = newHttpClient();
client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
client.setThreadPool(newQueuedThreadPool(50));
client.setMaxConnectionsPerAddress(10);
client.setTimeout(5000);
//啟動之前先配置好
client.start();
HttpExchange exchange = newHttpExchange();
exchange.setMethod("POST");
exchange.setURL("http://192.168.0.158:40404/test/");
exchange.setRequestHeader("check", "123456");
client.send(exchange);
}
}
2.2.2HTTP對外認證的實現
2.2.2.1 對外認證需求
在http實現的過程中,對外開放的url中有些只能被特定的用戶訪問,為了避免所有的用戶都能夠訪問所有的url,我們需要對不同的用戶設置不同的權限,來限制某些用戶訪問其權限范圍之外的url。
2.2.2.2 對外認證實現方式
為了給不同的用戶賦予不同的權限,需要給每個用戶名設置用戶名和密碼。所以需要使用數據庫記錄相關用戶名,密碼以及權限。
用戶在向服務器發送請求的同時,需要向服務器發送自己的用戶名及密碼,服務器對用戶名及密碼進行驗證從而確定該用戶是否合法以及該用戶是否有訪問該url的權限。
對於調用直接調用程序接口訪問url的用戶來說,可以通過post方式,將用戶名和密碼放到請求體(請求內容)中發送給HTTP服務器。服務端接收到請求內容后進行解析。例如:
Server端:
package test;
importjava.io.FileNotFoundException;
import org.eclipse.jetty.server.Connector;
importorg.eclipse.jetty.server.Handler;
importorg.eclipse.jetty.server.Server;
importorg.eclipse.jetty.server.handler.ContextHandler;
importorg.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.DefaultHandler;
importorg.eclipse.jetty.server.handler.HandlerCollection;
importorg.eclipse.jetty.server.nio.SelectChannelConnector;
public classSServer {
private Server server = null;
public static final String HOST = "192.168.0.158";
public static final int POST = 40404;
public static void main(String[] args)throws FileNotFoundException{
new SServer().startHttpServer();
}
public void startHttpServer() throwsFileNotFoundException {
server = new Server();
SelectChannelConnector connector= new SelectChannelConnector();
connector.setHost(HOST);
connector.setPort(POST);
server.setConnectors(newConnector[] { connector });
ContextHandler test = newContextHandler();
test.setContextPath("/test");
Handler handler = newHHandler();
test.setHandler(handler);
ContextHandlerCollectioncontexts = new ContextHandlerCollection();
contexts.setHandlers(newHandler[] { test });
HandlerCollection handlers = newHandlerCollection();
handlers.setHandlers(newHandler[] { contexts, new DefaultHandler() });
server.setHandler(handlers);
start();
}
private void start() {
try {
server.start();
server.join();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Handle類:
package test;
importjava.io.ByteArrayOutputStream;
importjava.io.IOException;
importjava.io.InputStream;
importjavax.servlet.ServletException;
importjavax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
importorg.eclipse.jetty.http.HttpStatus;
importorg.eclipse.jetty.server.Request;
importorg.eclipse.jetty.server.handler.AbstractHandler;
public classHHandler extends AbstractHandler {
public HHandler() {
}
@Override
public void handle(final String target,final Request request, final HttpServletRequest servlet_request,
final HttpServletResponse response)throws IOException, ServletException {
request.setHandled(true);
InputStream inputStream = request.getInputStream();
ByteArrayOutputStream out = newByteArrayOutputStream();
byte[] b = new byte[1024];
int i = 0;
while ((i = inputStream.read(b, 0,1024)) > 0) {
out.write(b, 0, i);
}
// 處理獲取到的數據
try {
String str=out.toString();
if(str.equals("www:123456")){
System.out.println(str);
response.getWriter().write("succeed");
response.setStatus(HttpStatus.OK_200);
}
} catch (Exception e){//NotExistsException
response.getWriter().write("Serverrespone error:" + e.getMessage());
response.setStatus(HttpStatus.FAILED_DEPENDENCY_424);
} finally {
out.close();
inputStream.close();
b = null;
}
response.getWriter().flush();
response.getWriter().close();
}
}
Client端:
package test;
import org.eclipse.jetty.client.HttpClient;
importorg.eclipse.jetty.client.HttpExchange;
importorg.eclipse.jetty.io.ByteArrayBuffer;
importorg.eclipse.jetty.util.thread.QueuedThreadPool;
public classCClient {
/**
*@param args
*/
public static void main(String[] args)throwsException {
testClient("www","123456");
}
public static void testClient(String name,String pwd)throws Exception{
HttpClient client = newHttpClient();
client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
client.setThreadPool(newQueuedThreadPool(50));
client.setMaxConnectionsPerAddress(10);
client.setTimeout(5000);
//啟動之前先配置好
client.start();
HttpExchange exchange = newHttpExchange();
exchange.setMethod("POST");
exchange.setURL("http://192.168.0.158:40404/test/");
exchange.setRequestContent(newByteArrayBuffer(name+":"+pwd));
client.send(exchange);
}
}
對於瀏覽器用戶,有兩種方式向服務器發送用戶名和密碼:
1) 在請求的URL地址后以?的形式直接帶上用戶名和密碼交給服務器。如:http://127.0.0.1:8341/upload/testf001?name=abc&password=xyz;這種方式最大的缺點是將用戶名和密碼暴露在url中,容易被人獲取,沒有安全性保障。
2) 設置訪問界面,在界面中同時輸入用戶名,密碼,以及要訪問的url,點擊提交按鈕,以post方式提交用戶名及密碼,驗證通過后轉至相應url的頁面。
2.3 HTTP KEEP ALIVE實現
在HTTP1.0和HTTP1.1協議中都有對長連接的支持。其中HTTP1.0需要在request中增加”Connection: keep-alive“ header才能夠支持,而HTTP1.1默認支持。
http1.0請求與服務端的交互過程:
1) 客戶端發出帶有包含一個header:”Connection: keep-alive“的請求;
2) 服務端接收到這個請求后,根據http1.0和”Connection: keep-alive“判斷出這是一個長連接,就會在response的header中也增加”Connection: keep-alive“,同是不會關閉已建立的tcp連接。
3) 客戶端收到服務端的response后,發現其中包含”Connection:keep-alive“,就認為是一個長連接,不關閉這個連接。並用該連接再發送request。轉到第一步。
http1.1請求與服務端的交互過程:
客戶端發出http1.1的請求
1) 服務端收到http1.1后就認為這是一個長連接,會在返回的response設置Connection: keep-alive,同時不會關閉已建立的連接.
2) 客戶端收到服務端的response后,發現其中包含“Connection: keep-alive”,就認為是一個長連接,不關閉這個連接。並用該連接再發送request。轉到第一步。
所以對於http1.1版本來說,默認情況下就是長連接,我們沒有必要在客戶端的請求頭中設置“Connection: keep-alive”,當然我們也可以顯式地設置,如:
HttpClientclient = new HttpClient();
client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
client.setThreadPool(newQueuedThreadPool(50));
client.setMaxConnectionsPerAddress(10);
client.setTimeout(5000);
//啟動之前先配置好
client.start();
HttpExchange exchange = newHttpExchange();
exchange.setMethod("POST");
exchange.setURL("http://192.168.0.158:40404/test/");
exchange.setRequestHeader("Connection","Keep-Alive");
client.send(exchange);
基於http協議的長連接減少了請求,減少了建立連接的時間,但是每次交互都是由客戶端發起的,客戶端發送消息,服務端才能返回客戶端消息.因為客戶端也不知道服務端什么時候會把結果准備好,所以客戶端的很多請求是多余的,僅是維持一個心跳,浪費了帶寬.
2.4 HTTPS的實現
2.4.1生成keystore
切換到JAVA_HOME/bin目錄下
使用keytool命令,創建keystore。例如:
./keytool -keystorefstoreKS -alias fstore -genkey -keyalg RSA
Enter keystorepassword:
Re-enter newpassword:
What is your firstand last name?
[Unknown]: fstore
What is the name ofyour organizational unit?
[Unknown]: fstore
What is the name ofyour organization?
[Unknown]: fstore
What is the name ofyour City or Locality?
[Unknown]: bj
What is the name ofyour State or Province?
[Unknown]: bj
What is thetwo-letter country code for this unit?
[Unknown]: cn
Is CN=fstore,OU=fstore, O=fstore, L=bj, ST=bj, C=cn correct?
[no]: yes
Enter key passwordfor <fstore>
(RETURN if same as keystore password):
Re-enter newpassword:
需要用戶輸入keystore密碼以及key密碼,最后,會在當前目錄下生成一個fstoreKS文件,這就是keystore文件
2.4.2服務端設置ssl連接
為jetty Server設置ssl Connector,需要指定keystore路徑, keystore密碼以及key密碼。樣例程序如下:
Server server = new Server();
SslSelectChannelConnector ssl_connector =new SslSelectChannelConnector();
ssl_connector.setHost("192.168.0.158");
ssl_connector.setPort(40404);
ContextHandler test = newContextHandler();
test.setContextPath("/test");
Handler handler = new HHandler();
test.setHandler(handler);
ContextHandlerCollection contexts = newContextHandlerCollection();
contexts.setHandlers(new Handler[] {test });
HandlerCollection handlers = newHandlerCollection();
handlers.setHandlers(new Handler[] {contexts, new DefaultHandler() });
server.setHandler(handlers);
SslContextFactory cf =ssl_connector.getSslContextFactory();
cf.setKeyStore("D:/fstoreKS");
cf.setKeyStorePassword("123456");
cf.setKeyManagerPassword("123456");
server.addConnector(ssl_connector);
try {
server.start();
server.join();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
2.4.3https客戶端實現
2.4.3.1 獲取證書並導入本地keystore
1) 用IE打開需要連接的https網址,會彈出如下對話框:
2) 單擊"查看證書",在彈出的對話框中選擇"詳細信息",然后再單擊"復制到文件",根據提供的向導生成待訪問網頁的證書文件;
3) 向導第一步,歡迎界面,直接單擊"Next";
4) 向導第二步,選擇導出的文件格式,默認,單擊"Next";
5) 向導第三步,輸入導出的文件名,輸入后,單擊"Next";
6) 向導第四步,單擊"Finish",完成向導;
7) 最后彈出一個對話框,顯示導出成功
8) 用keytool工具把剛才導出的證書倒入本地keystore。如:
keytool -import -noprompt -keystore mycacerts -storepass 123456-alias fstore -file fstore_ks.cer
其中mycacerts是要導出到的keystore文件名,可自己命名;123456是指定密鑰庫的密碼,自己設置;fstore是keystore關聯的獨一無二的別名,自己設置;fstore_ks.cer是上面生成證書的路徑,根據實際情況設置。
2.4.3.1 編寫客戶端程序
在程序中通過System.setProperty()方法設置keystore路徑,並把要訪問的url路徑的http改為https即可。示例程序如下:
packagetest;
importorg.eclipse.jetty.client.HttpClient;
importorg.eclipse.jetty.client.HttpExchange;
importorg.eclipse.jetty.io.ByteArrayBuffer;
importorg.eclipse.jetty.util.ssl.SslContextFactory;
importorg.eclipse.jetty.util.thread.QueuedThreadPool;
publicclass CClient {
/**
*@param args
*/
public static void main(String[] args)throwsException {
testClient("www","123456");
}
public static void testClient(String name,String pwd)throws Exception{
SslContextFactory sslCF=newSslContextFactory();
sslCF.setTrustAll(false);
System.setProperty("javax.net.ssl.trustStore","D:/mycacerts");
HttpClient client = newHttpClient(sslCF);
client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
client.setThreadPool(newQueuedThreadPool(50));
client.setMaxConnectionsPerAddress(10);
client.setTimeout(5000);
//啟動之前先配置好
client.start();
HttpExchange exchange = new HttpExchange();
exchange.setMethod("POST");
exchange.setURL("https://192.168.0.158:40404/test/");
exchange.setRequestContent(newByteArrayBuffer(name+":"+pwd));
client.send(exchange);
}
}
2.5 HTTP curl上傳以及瀏覽器上傳實現
2.5.1 需求分析
在基於http的開發中,有時我們需要通過瀏覽器和curl命令上傳本地文件到服務器,與直接通過jetty的HttpClient上傳文件內容不同,通過瀏覽器和curl上傳本地時,請求內容中不僅僅只是文件內容,同時包含一些額外的http信息,如:
--ghFsUA_25eQJixJIK-SxXGwGsn_HTc4zaS
Content-Disposition:form-data; name="filename"; filename="we.txt"
Content-Type:application/octet-stream; charset=ISO-8859-1
Content-Transfer-Encoding:binary
…………
--ghFsUA_25eQJixJIK-SxXGwGsn_HTc4zaS--
所以我們需要區分實際要上傳的文件內容和無關信息,從而實現瀏覽器和curl命令上傳本地文件到服務器。
2.5.2 實現方式
服務器處理請求時不能采用原先的Handler方式,而是jetty服務端設置ServletContext,通過在Servlet程序中重寫doPost方法來實現,在doPost方法中取出對應文件內容的part中的內容,並進行處理。
程序示例如下:
Server端:
packagecn.ac.iie.fstore.http.server;
importjava.io.FileNotFoundException;
importmy.javax.servlet.MultipartConfigElement;
importmy.org.eclipse.jetty.server.Connector;
import my.org.eclipse.jetty.server.Handler;
importmy.org.eclipse.jetty.server.Server;
importmy.org.eclipse.jetty.server.handler.ContextHandlerCollection;
importmy.org.eclipse.jetty.server.handler.DefaultHandler;
import my.org.eclipse.jetty.server.handler.HandlerCollection;
importmy.org.eclipse.jetty.server.nio.SelectChannelConnector;
importmy.org.eclipse.jetty.servlet.ServletContextHandler;
importmy.org.eclipse.jetty.servlet.ServletHolder;
public classSServer {
private Server server = null;
public static final String HOST ="192.168.0.158";
public static final int POST = 40404;
public static void main(String[] args)throws FileNotFoundException{
new SServer().startHttpServer();
}
public void startHttpServer() throwsFileNotFoundException {
server = new Server();
SelectChannelConnector connector = newSelectChannelConnector();
connector.setHost(HOST);
connector.setPort(POST);
server.setConnectors(new Connector[] {connector });
ServletContextHandler test = newServletContextHandler(ServletContextHandler.SESSIONS);
test.setContextPath("/test");
ServletHolder mainHolder = newServletHolder(new HHandler());
mainHolder.getRegistration().setMultipartConfig(
new MultipartConfigElement("data/tmp",250 * 1024 * 1024, 250 * 1024 * 1024, 250 * 1024 * 1024));
test.addServlet(mainHolder,"/*");
ContextHandlerCollection contexts = newContextHandlerCollection();
contexts.setHandlers(new Handler[] {test });
HandlerCollection handlers = newHandlerCollection();
handlers.setHandlers(new Handler[] {contexts, new DefaultHandler() });
server.setHandler(handlers);
start();
}
private void start() {
try {
server.start();
server.join();
} catch (Exception e) {
e.printStackTrace();
}
}
}
處理類:
packagecn.ac.iie.fstore.http.server;
importjava.io.ByteArrayOutputStream;
importjava.io.IOException;
importjava.io.InputStream;
importmy.javax.servlet.ServletException;
importmy.javax.servlet.http.HttpServlet;
importmy.javax.servlet.http.HttpServletRequest;
importmy.javax.servlet.http.HttpServletResponse;
importmy.javax.servlet.http.Part;
importmy.org.eclipse.jetty.http.HttpStatus;
importcn.ac.iie.fstore.http.util.HttpServerContantVal;
importcn.ac.iie.fstore.util.ConstantVal;
public classHHandler extends HttpServlet {
private final int fileNameLength=250;
@Override
protected void doPost(HttpServletRequestrequest, HttpServletResponse response) throws ServletException, IOException {
// 獲得上傳文件的輸入流
Part part =request.getParts().iterator().next();
// Part part =request.getPart(HttpServerContantVal._FILENAME);
final InputStream inputStream =part.getInputStream();
final ByteArrayOutputStream outputStream= new ByteArrayOutputStream();
// 獲得上傳文件的長度
// final int dataLength = (int)part.getSize();
String filename =request.getPathInfo().substring(1);
String namespace =request.getParameter(HttpServerContantVal._NAMESPACE);
if (null == filename ||filename.trim().length() == 0) {
String partName = part.getName();
if (partName != null &&partName.trim().length() > 0) {
filename = partName;
}else{
response.setStatus(HttpStatus.FORBIDDEN_403);
response.getWriter().write("Checkfile name and name length");
response.getWriter().flush();
response.getWriter().close();
return;
}
}
if (filename.trim().length() >=fileNameLength) {
response.setStatus(HttpStatus.FORBIDDEN_403);
response.getWriter().write("Filename is too long");
response.getWriter().flush();
response.getWriter().close();
return;
}
if (null == namespace ||namespace.trim().length() == 0) {
namespace = ConstantVal.DEFAULTPATH;
}
// 讀取文件內容到緩沖
byte[] b = new byte[1024];
int i = 0;
while ((i = inputStream.read(b, 0,1024)) > 0) {
outputStream.write(b, 0, i);
}
try {
System.out.println(outputStream.toString());
response.setStatus(HttpStatus.OK_200);
response.getWriter().write("OK");
response.getWriter().flush();
response.getWriter().close();
} catch (Exception e) {
response.setStatus(HttpStatus.FORBIDDEN_403);
response.getWriter().write("Forbidden\nFstoreerror");
response.getWriter().flush();
response.getWriter().close();
}
}
}
Html腳本:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>download file bypost</title>
</head>
<body>
<form id="form1"action="http://192.168.0.158:40404/test/" method="POST"enctype ="multipart/form-data">
<input id="filename"type="file" name="filename" />
<input type="submit"value="submit1" />
</form>
</body>
</html>
在這種方式下,低版本的jetty是不支持的,因為
mainHolder.getRegistration().setMultipartConfig(newMultipartConfigElement("data/tmp", 250 * 1024 * 1024, 250 * 1024 *1024, 250 * 1024 * 1024));方法是從jetty v8.x以后才支持的。