- Server
jetty启动类
Server server = new Server(int port);
Server server = new Server();
server.setPort(8888); - Listener监听器
每一个组件中都会有一个监听器,监听请求的到来。如果没有指定的Listener的话,则使用默认的Listener
HttpListener是所有监听器类的接口,很多都继承了他,SocketListener基于传统的socket技术, SocketChannelListener基于NIO技术
- ThreadPool线程池
如果有设置有请求就从中获取资源,否则new一个QueuedThreadPoolserver.setThreadPool(new ExecutorThreadPool(200, 200, 30000)); // 非阻塞
- Connertor连接器
connector与传输层socket有关,但是因为socket有BIO和NIO两种类型,所以connector分别对应了SocketConnector和AbstractNioConnector
Connector收到socket请求之后,产生HttpConnection对象(解析http协议的地方),httpconnection提供了request和response对象
Connector这里还与threadPool有关系,connector接收到一个请求后,从threadPool提取一个线程来处理这个请求总之:Connector职责是接收来自客户端的请求,根据HTTP协议解析请求的内容,指派一个线程去处理请求并响应结果
// connector SelectChannelConnector connector = new SelectChannelConnector(); connector.setPort(port); connector.setMaxIdleTime(30000); server.setConnectors(new Connector[] { connector });
-
Handler处理器
当connector收到请求的时候,就必须要有处理器来处理请求
几乎所有自定义的handler都继承abstractHandler这个抽象类,这是因为abstractHandler里面有个重要的方法来处理请求;其中var1指客户端请求地址,request和response则为 connector监听请求时HttpConnection产生的实现void handle(String var1, Request var2, HttpServletRequest var3, HttpServletResponse var4) throws IOException, ServletException;
Handler接口可以分为三类:
内容输出Handler ———— 该类Handler根据target输出内容;ResourceHandler、DefaultHandler、Redirector这些类直接对handle接口进行实现装饰模式Handler(HandlerWrapper) ———— 继承自 HandlerWrapper类setHandler方法便是用来设定被代理Handler对象,将多个handler串联起来
集合类Handler(HandlerCollection)————— 作用是将请求分发给其他Handler处理常用集合类Handler有 HandlerCollection、HandlerList、ContextHandlerCollection
// handler HandlerCollection handlerc =new HandlerCollection(); handlerc.setHandlers(new Handler[]{new 自定义handler()}); server.setHandler(handlerc);
//多个handler HandlerList handlers = new HandlerList(); handlers.addHandler(自定义handler);
Jetty流程:
首先Connector收到一个请求,Connector根据HTTP/1.1协议将请求包装成为 Handler所认识的Request和Response对象(这里httpConnection参与了),然后用Server提供的ThreadPool对象来分配一个线程去执行Handler调 用(这里即开始接受request请求),注意Server对象算是一个Handler对象,所以从Server开始去交给其他Handler对象处理 准确的说:Listener——connector——httpConnection——server——httpContxt——handler
Jetty Example 1:
依赖:
<lombok.version>1.16.4</lombok.version> <latest.release.version>2.0.5-SNAPSHOT</latest.release.version> <guava.version>18.0</guava.version> <jersey.version>1.19</jersey.version> <junit.version>4.12</junit.version> <unitils.core.version>3.4.2</unitils.core.version> <mockito.version>1.10.19</mockito.version>
<!-- jersey jetty --> <dependency> <groupId>com.sun.jersey</groupId> <artifactId>jersey-servlet</artifactId> </dependency> <dependency> <groupId>com.sun.jersey</groupId> <artifactId>jersey-json</artifactId> </dependency> <dependency> <groupId>org.eclipse.jetty.aggregate</groupId> <artifactId>jetty-all-server</artifactId> </dependency> <!-- guava --> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> </dependency> <!-- test --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> </dependency> <dependency> <groupId>org.unitils</groupId> <artifactId>unitils-core</artifactId> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> </dependency> <!-- lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
RestfulServer启动Jetty,并注册JerseyServlet会扫描@Provider注解下的自定义解析器、异常等
package com.lianpo.rpc.restful; import com.google.common.base.Joiner; import com.google.common.base.Optional; import com.sun.jersey.api.core.PackagesResourceConfig; import com.sun.jersey.spi.container.servlet.ServletContainer; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.HandlerList; import org.eclipse.jetty.server.handler.ResourceHandler; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.util.resource.Resource; /** * Created by liz on 2017/1/19. * * @auther liz */ public class RestfulServer { //jetty server private final Server server; public RestfulServer(int port) { this.server = new Server(port); } /** * 启动jetty */ public void start(final String packages, final Optional<String> resourcePath) throws Exception { // HandlerList handlers = new HandlerList(); if (resourcePath.isPresent()) { handlers.addHandler(buildResourceHandler(resourcePath.get())); } handlers.addHandler(buildServletContextHandler(packages)); server.setHandler(handlers); server.start(); } /** * 使用ResourceHanlder来处理用户对静态资源的请求 * ResourceHandler可以处理的包括 html htm 等浏览器可以解释的 文件类型也可以处理txt 和图片 只要是能显示的内容 都可以在浏览器里显示 * 如果文件类型不能解释 也不能在浏览器里显示的话 则会提示是否下载 * ResourceHandler不能处理jsp页面 等动态页面 */ private ResourceHandler buildResourceHandler(final String resourcePath) { ResourceHandler resourceHandler = new ResourceHandler(); resourceHandler.setBaseResource(Resource.newClassPathResource(resourcePath)); return resourceHandler; } /** * 创建一个handler */ private ServletContextHandler buildServletContextHandler(final String packages) { ServletContextHandler servletContextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS); //上下文path servletContextHandler.setContextPath("/"); //add servlet servletContextHandler.addServlet(getServletHolder(packages), "/*"); return servletContextHandler; } /** * 创建一个JerseyServlet */ private ServletHolder getServletHolder(final String packages) { ServletHolder servletHolder = new ServletHolder(ServletContainer.class); //系统启动时扫描的包的服务端的路径 servletHolder.setInitParameter(PackagesResourceConfig.PROPERTY_PACKAGES, Joiner.on(",").join(RestfulServer.class.getPackage().getName(), packages)); servletHolder.setInitParameter("com.sun.jersey.config.property.resourceConfigClass", PackagesResourceConfig.class.getName()); //是否打开Json POJO servletHolder.setInitParameter("com.sun.jersey.api.json.POJOMappingFeature", Boolean.TRUE.toString()); //扫描@Provider注解并注册 //servletHolder.setInitParameter("resteasy.scan.providers", Boolean.TRUE.toString()); //是否注册resteasy默认值,内置@Provider classes. //servletHolder.setInitParameter("resteasy.use.builtin.providers", Boolean.FALSE.toString()); return servletHolder; } /** * 关闭jetty */ public void stop() throws Exception { if (server != null) { server.stop(); } } }
FastJsonProvider自定义解析器
package com.lianpo.rpc.restful; import com.google.common.base.Strings; import com.alibaba.fastjson.JSON; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.lang.annotation.Annotation; import java.lang.reflect.Type; import javax.ws.rs.Consumes; import javax.ws.rs.Produces; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.ext.MessageBodyReader; import javax.ws.rs.ext.MessageBodyWriter; import javax.ws.rs.ext.Provider; /** * Created by liz on 2017/1/19. * * @auther liz * MessageBodyWriters 被 JAX-RS 运行时用来序列化所返回资源 * MessageBodyReader 的最主要的功能是读取请求 InputStream 并将传入的字节反序列化到一个此资源方法期望的 Java 对象 */ @Provider @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public final class FastJsonProvider implements MessageBodyWriter<Object>, MessageBodyReader<Object> { private static final String UTF_8 = "UTF-8"; /** * 这里一定要放回true,解析才会调用readFrom * @param aClass * @param type * @param annotations * @param mediaType * @return */ @Override public boolean isReadable(Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType) { return true; } /** * 将流反序列化对应的对象 * @param type * @param genericType * @param annotations * @param mediaType * @param multivaluedMap * @param inputStream * @return * @throws IOException * @throws WebApplicationException */ @Override public Object readFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> multivaluedMap, InputStream inputStream) throws IOException, WebApplicationException { try { InputStreamReader streamReader = new InputStreamReader(inputStream, UTF_8); StringBuilder result = new StringBuilder(); BufferedReader reader = new BufferedReader(streamReader); try{ String line; while (!Strings.isNullOrEmpty(line = reader.readLine())){ result.append(line); } }finally { streamReader.close(); reader.close(); } return JSON.parseObject(result.toString(), type.equals(genericType) ? type : genericType); } catch (final IOException ex) { throw new RestfulException(ex); } } /** * 一定要返回true,解析才会调用writeTo * @param aClass * @param type * @param annotations * @param mediaType * @return */ @Override public boolean isWriteable(Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType) { return true; } @Override public long getSize(Object o, Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType) { return -1; } /** * 将对象写入流中 * @param object * @param type * @param genericType * @param annotations * @param mediaType * @param multivaluedMap * @param outputStream * @throws IOException * @throws WebApplicationException */ @Override public void writeTo(Object object, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> multivaluedMap, OutputStream outputStream) throws IOException, WebApplicationException { try{ OutputStreamWriter outputWriter = new OutputStreamWriter(outputStream); outputWriter.write(JSON.toJSONString(object)); outputWriter.close(); }catch (final IOException ex){ throw new RestfulException(ex); } } }
RestfulExceptionMapper异常Mapper
package com.lianpo.rpc.restful; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; /** * Created by liz on 2017/1/19. * * @auther liz */ @Provider public final class RestfulExceptionMapper implements ExceptionMapper<Throwable> { @Override public Response toResponse(Throwable throwable) { return Response.ok(throwable.getMessage(), MediaType.TEXT_PLAIN).status(Response.Status.INTERNAL_SERVER_ERROR).build(); } }
RestfulException自定义异常
package com.lianpo.rpc.restful; /** * Created by liz on 2017/1/19. * * @auther liz */ public final class RestfulException extends RuntimeException { private static final long serialVersionUID = -7424564317548550506L; public RestfulException(final Throwable throwable){ super(throwable); } }
测试类
Caller
package com.lianpo.rpc.restful.fix; /** * Created by liz on 2017/1/19. * * @auther liz */ public interface Caller { void call(String value); void call(int value); }
TestRestApi
package com.lianpo.rpc.restful.fix; import com.google.common.collect.Maps; import java.util.Map; import javax.ws.rs.POST; import javax.ws.rs.Path; import lombok.Setter; /** * Created by liz on 2017/1/19. * * @auther liz */ @Path("/test") public final class TestRestApi { @Setter private static Caller caller; @POST @Path("/call") public final Map<String,String> call(Map<String,String> map){ caller.call(map.get("key")); caller.call(Integer.valueOf(map.get("value"))); System.out.println("==============" + map); return Maps.transformEntries(map, new Maps.EntryTransformer<String, String, String>() { @Override public String transformEntry(final String key, final String value) { return value + "_processed"; } }); } }
RestfulServerTest
package com.lianpo.rpc.restful; import com.google.common.base.Joiner; import com.google.common.base.Optional; import com.lianpo.rpc.restful.fix.Caller; import com.lianpo.rpc.restful.fix.TestRestApi; import org.eclipse.jetty.client.ContentExchange; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.io.ByteArrayBuffer; import org.hamcrest.core.Is; import org.junit.AfterClass; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; import org.mockito.runners.MockitoJUnitRunner; import javax.ws.rs.core.MediaType; /** * Created by liz on 2017/1/19. * * @auther liz */ @RunWith(MockitoJUnitRunner.class) public class RestfulServerTest { private static RestfulServer restfulServer; private static Caller caller; private static final String URL = "http://127.0.0.1:8000/test/call"; private static final int port = 8000; @BeforeClass public static void setUpClass() throws Exception { restfulServer = new RestfulServer(port); restfulServer.start(TestRestApi.class.getPackage().getName(), Optional.<String>absent()); } @AfterClass public static void setDown() throws Exception { restfulServer.stop(); } @Before public void setUp(){ caller = Mockito.mock(Caller.class); TestRestApi.setCaller(caller); } @Test public void assertCallSuccessOrFail() throws Exception { ContentExchange actual = sendRequest("{\"key\":\"test\",\"value\":1}"); Assert.assertThat(actual.getResponseStatus(), Is.is(200)); Assert.assertThat(actual.getResponseContent(), Is.is("{\"key\":\"test_processed\",\"value\":\"1_processed\"}")); Mockito.verify(caller).call("test"); Mockito.verify(caller).call(1); //fail Mockito.verify(caller).call(2); } @Test public void test(){ String packages = Joiner.on(",").join(RestfulServer.class.getPackage().getName(), TestRestApi.class.getPackage().getName()); System.out.println(packages); } public static ContentExchange sendRequest(final String content) throws Exception { HttpClient client = new HttpClient(); try { client.start(); ContentExchange contentExchange = new ContentExchange(); contentExchange.setMethod("POST"); contentExchange.setRequestContentType(MediaType.APPLICATION_JSON); contentExchange.setRequestContent(new ByteArrayBuffer(content.getBytes("UTF-8"))); client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); contentExchange.setURL(URL); client.send(contentExchange); contentExchange.waitForDone(); return contentExchange; } finally { client.stop(); } } }
Jetty Example 2:
RestServer:
package com.lianpo.rpc.restful; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.HandlerCollection; import org.eclipse.jetty.server.nio.SelectChannelConnector; import org.eclipse.jetty.util.thread.ExecutorThreadPool; /** * Created by liz on 2017/1/19. * * @auther liz */ public class RestServer { private static Server server; private static int port; public RestServer(int port) { this.port = port; this.server = new Server(); } public void start() throws Exception { new Thread(new Runnable() { @Override public void run() { //pool server.setThreadPool(new ExecutorThreadPool(200, 200, 30000));//非阻塞 //connector //Nio SelectChannelConnector connector = new SelectChannelConnector(); connector.setPort(RestServer.port); connector.setMaxIdleTime(30000); server.setConnectors(new Connector[]{connector}); //handler HandlerCollection handlerCollection = new HandlerCollection(); handlerCollection.setHandlers(new Handler[]{new CallHandler()}); server.setHandler(handlerCollection); try { server.start(); //线程阻塞,保证server的启动 server.join(); } catch (Exception e) { e.printStackTrace(); } } }).start(); } public void stop() throws Exception { if (server != null) { server.stop(); } } }
CallHandler:
package com.lianpo.rpc.restful; import com.google.common.base.Strings; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.AbstractHandler; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Created by liz on 2017/1/19. * * @auther liz */ public class CallHandler extends AbstractHandler { private static final String UTF_8 = "UTF-8"; @Override public void handle(String s, Request request, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException, ServletException { System.out.println("===================" + s); InputStreamReader streamReader = new InputStreamReader(httpServletRequest.getInputStream(), UTF_8); StringBuilder result = new StringBuilder(); BufferedReader reader = new BufferedReader(streamReader); String line; while (!Strings.isNullOrEmpty(line = reader.readLine())){ result.append(line); } streamReader.close(); reader.close(); System.out.println("---------------------------" + result); httpServletResponse.setContentType("text/html;charset=utf-8"); httpServletResponse.setStatus(HttpServletResponse.SC_OK); request.setHandled(true); httpServletResponse.getWriter().print("成功"); } }
RestServerTest:
package com.lianpo.rpc.restful; import org.eclipse.jetty.client.ContentExchange; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.io.ByteArrayBuffer; import org.hamcrest.core.Is; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.runners.MockitoJUnitRunner; import javax.ws.rs.core.MediaType; /** * Created by liz on 2017/1/19. * * @auther liz */ @RunWith(MockitoJUnitRunner.class) public class RestServerTest { private static RestServer server; private static String URL = "http://127.0.0.1:8888/test/call"; @BeforeClass public static void setUpClass() throws Exception { server = new RestServer(8888); server.start(); } @AfterClass public static void setDown() throws Exception { server.stop(); } @Test public void assertCallSuccessOrFail() throws Exception { ContentExchange actual = sendRequest("{\"key\":\"test\",\"value\":1}"); Assert.assertThat(actual.getResponseStatus(), Is.is(200)); Assert.assertThat(actual.getResponseContent(), Is.is("成功")); } public static ContentExchange sendRequest(final String content) throws Exception { HttpClient client = new HttpClient(); try { client.start(); ContentExchange contentExchange = new ContentExchange(); contentExchange.setMethod("POST"); contentExchange.setRequestContentType(MediaType.APPLICATION_JSON); contentExchange.setRequestContent(new ByteArrayBuffer(content.getBytes("UTF-8"))); client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); contentExchange.setURL(URL); client.send(contentExchange); contentExchange.waitForDone(); return contentExchange; } finally { client.stop(); } } }