http協議是基於TCP的一種應用層協議,因爲我們可以自己通過tcp的數據來進行編解碼http協議。一般情況下,我們都是通過request對象和response對象進行與用戶進行交互的。
netty是一款對用戶非常友好的網絡框架,更甚者提供了大量的基於tcp的用戶層的協議封裝,我們可以直接使用,因爲在netty註冊編解碼器的時候,我們只需要加上netty自帶的http的編解碼器,就可以在接收和發送數據的時候採用request對象和response對象。
pipeline.addLast("decoder", new HttpRequestDecoder());
pipeline.addLast("aggregator", new HttpChunkAggregator(65536));
pipeline.addLast("encoder", new HttpResponseEncoder());
這樣子,初步的我們的netty已經可以與客戶端之間進行http協議的通訊,但是這種通訊是屬於靜態的內容的,沒有辦法與本文所說結合springmvc藉助springmvc的controller來處理業務邏輯。還記得springmvc的入口嗎?org.springframework.web.servlet.DispatcherServlet這個servlet就是springmvc的入口,其實思路就是我們只需要調用這個servlet的service方法,然後裏面的處理就會直接給springmvc接管了,因此我們要初始化這個servlet。
private final DispatcherServlet dispatcherServlet;
public DispatcherServletChannelInitializer() throws ServletException {
MockServletContext servletContext = new MockServletContext();
MockServletConfig servletConfig = new MockServletConfig(servletContext);
servletConfig.addInitParameter("contextConfigLocation","classpath:/META-INF/spring/root-context.xml");
servletContext.addInitParameter("contextConfigLocation","classpath:/META-INF/spring/root-context.xml");
//AnnotationConfigWebApplicationContext wac = new AnnotationConfigWebApplicationContext();
XmlWebApplicationContext wac = new XmlWebApplicationContext();//採用xml的配置形式加載spring文件,初始化springmvc的context
//ClassPathXmlApplicationContext wac = new ClassPathXmlApplicationContext();
wac.setServletContext(servletContext);
wac.setServletConfig(servletConfig);
wac.setConfigLocation("classpath:/servlet-context.xml");
//wac.register(WebConfig.class);
wac.refresh();
this.dispatcherServlet = new DispatcherServlet(wac);
this.dispatcherServlet.init(servletConfig);
}
已經存在servlet,要調用servlet的service接口,讓我們看看servlet的service方法定義。 protected void service(HttpServletRequest request, HttpServletResponse response)
這個service方法的參數是HttpServletRequest和HttpServletResponse,因爲我們接下來的目標就是將netty的HttpRequest和HttpResponse對象與servlet的對象進行相互轉換。我採用了springtest提供的用來模擬HttpServletRequest和HttpServletResponse的類,這兩個類也是它們的子類。
org.springframework.mock.web.MockHttpServletRequest;org.springframework.mock.web.MockHttpServletResponse;
@Override
public void messageReceived(ChannelHandlerContext ctx, HttpRequest request) throws Exception {
if (!request.getDecoderResult().isSuccess()) {
sendError(ctx, BAD_REQUEST);
return;
}
MockHttpServletRequest servletRequest = createServletRequest(request);
MockHttpServletResponse servletResponse = new MockHttpServletResponse();
this.servlet.service(servletRequest, servletResponse);
HttpResponseStatus status = HttpResponseStatus.valueOf(servletResponse.getStatus());
HttpResponse response = new DefaultHttpResponse(HTTP_1_1, status);
for (String name : servletResponse.getHeaderNames()) {
for (Object value : servletResponse.getHeaderValues(name)) {
response.addHeader(name, value);
}
}
// Write the initial line and the header.
ctx.write(response);
InputStream contentStream = new ByteArrayInputStream(servletResponse.getContentAsByteArray());
// Write the content.
ChannelFuture writeFuture = ctx.write(new ChunkedStream(contentStream));
writeFuture.addListener(ChannelFutureListener.CLOSE);
}
最後直接在netty的回調方法中,進行處理和轉換就OK了。遺憾的是,因爲在代碼裏面並沒有整合jsp和servlet容器,容易沒辦法處理正常的jsp頁面,只能夠將其作爲http服務發佈。
項目的完整代碼已經在https://github.com/linsongze/nettyholdspringmvc/。