在介绍嵌入式jetty之前,首先介绍一下jetty。
Jetty 是一个开源的servlet容器,它为基于Java的web内容,例如JSP和servlet提供运行环境。Jetty是使用Java语言编写的,它的API以一组JAR包的形式发布。开发人员可以将Jetty容器实例化成一个对象,可以迅速为一些独立运行(stand-alone)的Java应用提供网络和Web连接。
Jetty主要有以下特性:
易用性
易用性是 Jetty 设计的基本原则,易用性主要体现在以下几个方面:
通过 XML 或者 API 来对Jetty进行配置;默认配置可以满足大部分的需求;将 Jetty 嵌入到应用程序当中只需要非常少的代码;
可扩展性
在使用了 Ajax 的 Web 2.0 的应用程序中,每个连接需要保持更长的时间,这样线程和内存的消耗量会急剧的增加。这就使得我们担心整个程序会因为单个组件陷入瓶颈而影响整个程序的性能。但是有了 Jetty:即使在有大量服务请求的情况下,系统的性能也能保持在一个可以接受的状态。利用Continuation 机制来处理大量的用户请求以及时间比较长的连接。 另外 Jetty 设计了非常良好的接口,因此在 Jetty 的某种实现无法满足用户的需要时,用户可以非常方便地对 Jetty 的某些实现进行修改,使得 Jetty 适用于特殊的应用程序的需求。
易嵌入性
Jetty 设计之初就是作为一个优秀的组件来设计的,这也就意味着 Jetty 可以非常容易的嵌入到应用程序当中而不需要程序为了使用 Jetty 做修改。从某种程度上,你也可以把 Jetty 理解为一个嵌入式的Web服务器。
Jetty有一个口号:不要把你的应用部署到Jetty中,把Jetty部署到你的应用中。Jetty可以在Java应用程序中向其他POJO一样被实例化,换句话说,以嵌入式的模式运行Jetty是指将Http模块放入你的应用程序中,而非部署你的程序到一个HTTP服务器。这就是所谓的嵌入式jetty。
JETTY嵌入式Web容器体积小,启动速度快,性能好,免费开源,是一款很适合开发调试和简单演示的轻量级Web容器。而且它的嵌入式的特点,使得开发者可以直接将容器的操作包含在程序逻辑里,得以摆脱TOMCAT,JBOSS等独立容器带来的安装,维护,部署等一系列令人头疼的问题。
在代码中嵌入一个Jetty server,通常需要以下步骤:
l 创建Server
l 添加/配置Connectors
l 添加/配置Handlers
l 添加/配置Servlets/Webapps到Handlers
l 启动服务器并join
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();
}
}
创建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服务器的实现的步骤通常为:
为了对每个请求作出相应,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();
}
server.start();
server.join();
至此,嵌入式jetty的Server端基本编写方式已经完成,然而复杂的请求是建立在很多Handlers的基础上的。下面将详细介绍Handlers的其他内容。
复杂的请求处理通常是建立在多个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。
下面的代码使用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错误页面。
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();
}
}
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());
}
}
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();
}
}
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();
}
}
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();
}
}
在和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服务器特定地址的设置,所以它可以同时连接多个服务器。
一旦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之前执行。
HttpExchange提供了以下可被重写的回调函数来判断一个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");
}
}
异步请求在性能上表现的最好,然而有时候我们需要使用同步请求来实现自己的需求,我们可以使用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成功执行完,或者发生错误,或者响应超时。
在http实现的过程中,有些url只用于程序内部的httpClient访问,这些url不需要或者说不能对外开放,这就需要程序内部得有这样一个机制,对于某些特定的url只允许程序内部访问,不允许在程序外部进行访问。
为实现上述需求,我觉得一个很好的解决办法是,对于程序内部的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);
}
}
在http实现的过程中,对外开放的url中有些只能被特定的用户访问,为了避免所有的用户都能够访问所有的url,我们需要对不同的用户设置不同的权限,来限制某些用户访问其权限范围之外的url。
为了给不同的用户赋予不同的权限,需要给每个用户名设置用户名和密码。所以需要使用数据库记录相关用户名,密码以及权限。
用户在向服务器发送请求的同时,需要向服务器发送自己的用户名及密码,服务器对用户名及密码进行验证从而确定该用户是否合法以及该用户是否有访问该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的页面。
在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协议的长连接减少了请求,减少了建立连接的时间,但是每次交互都是由客户端发起的,客户端发送消息,服务端才能返回客户端消息.因为客户端也不知道服务端什么时候会把结果准备好,所以客户端的很多请求是多余的,仅是维持一个心跳,浪费了带宽.
切换到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文件
为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();
}
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是上面生成证书的路径,根据实际情况设置。
在程序中通过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);
}
}
在基于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命令上传本地文件到服务器。
服务器处理请求时不能采用原先的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以后才支持的。
原文地址:http://blog.csdn.net/l_h2010/article/details/43197083