一个简单的netty springMVC http服务器

说一下最基本的的需求,用netty做网络处理,写一个最基本的http server,它含有以下功能

  1.  能快速处理各种请求GET/POST,并返回对应的内容,此时,它就是一个api服务器
  2.  能处理模板文件,根据模板内容,生成对应的html页面内容
  3.  能处理静态资源文件,例如下载文件,图片,js之类
  4.  必须足够简单

明确了以上目标后,我们可以开始动手设计了,我们需要以下内容

  • MAVEN 实际上其他类似工具gradle等也是可以的(以下所需的java库就无需手动下载了),但这里我们只使用maven
  • freemarker 这其实是一个java库,用于模板生成,当然,用其他例如velocity也是可以的,为了做到足够简单,我们用freemarker
  • netty 也是一个java库
  • spring mvc 同理,一个java库

现在可以开始动手写我们的HTTP服务器了,先说一下基本目录

首先是代码目录

                                                                       

  • 主要是一个controller包,写控制逻辑,
  • global包,关于模板配置的在这里
  • AppConfig.java 配置声明一下这个是spring mvc
  • MyHttp*,MyNetty*这些是关于netty处理http请求的
  • Web*Test.java,里面就一个main方法,用于启动测试

下面是资源目录

  • 一个template目录,存在html模板
  • 一个template/static, 放静态资源
  • 一个spring.xml配置spring一些内容

 现在放代码部分

首先是网络处理部分

package com._22evil.web_container;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com._22evil.web_container.global.Global;

/**
* Created by Administrator on 2016/12/28.
*/
public class MyHttpServer {
private final static Logger logger = LoggerFactory.getLogger(MyHttpServer.class);

private final int port;

public MyHttpServer(int port) {
this.port = port;
}

public void launch() throws Exception {
initFreeMarker();

ServerBootstrap server = new ServerBootstrap();
NioEventLoopGroup group = new NioEventLoopGroup();
try {
server.group(new NioEventLoopGroup(), new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
.localAddress(port)
.childHandler(new MyNettyInitalizer());

logger.info("Netty server has started on port : " + port);

server.bind().sync().channel().closeFuture().sync();
}
finally {
group.shutdownGracefully();
}
}

/**
* 初始化模板解析
*/
private void initFreeMarker() throws Exception{
Global.getFreeMarkerConfig();
}
 
}
package com._22evil.web_container;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.handler.stream.ChunkedStream;
import io.netty.util.CharsetUtil;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.UriUtils;

import javax.servlet.Servlet;
import javax.servlet.ServletContext;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.List;
import java.util.Map;

import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;

/**
* Created by Administrator on 2016/12/29.
*/
public class MyHttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
private final Servlet servlet;

private final ServletContext servletContext;

public MyHttpHandler(Servlet servlet) {
this.servlet = servlet;
this.servletContext = servlet.getServletConfig().getServletContext();
}

private MockHttpServletRequest createServletRequest(FullHttpRequest fullHttpRequest) {
UriComponents uriComponents = UriComponentsBuilder.fromUriString(fullHttpRequest.getUri()).build();

MockHttpServletRequest servletRequest = new MockHttpServletRequest(this.servletContext);
servletRequest.setRequestURI(uriComponents.getPath());
servletRequest.setPathInfo(uriComponents.getPath());
servletRequest.setMethod(fullHttpRequest.getMethod().name());

if (uriComponents.getScheme() != null) {
servletRequest.setScheme(uriComponents.getScheme());
}
if (uriComponents.getHost() != null) {
servletRequest.setServerName(uriComponents.getHost());
}
if (uriComponents.getPort() != -1) {
servletRequest.setServerPort(uriComponents.getPort());
}

for (String name : fullHttpRequest.headers().names()) {
servletRequest.addHeader(name, fullHttpRequest.headers().get(name));
}

// 将post请求的参数,添加到HttpServletRrequest的parameter
try {
ByteBuf buf=fullHttpRequest.content();
int readable=buf.readableBytes();
byte[] bytes=new byte[readable];
buf.readBytes(bytes);
String contentStr = UriUtils.decode(new String(bytes,"UTF-8"), "UTF-8");
for(String params : contentStr.split("&")){
String[] para = params.split("=");
if(para.length > 1){
servletRequest.addParameter(para[0],para[1]);
} else {
servletRequest.addParameter(para[0],"");
}
}
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

try {
if (uriComponents.getQuery() != null) {
String query = UriUtils.decode(uriComponents.getQuery(), "UTF-8");
servletRequest.setQueryString(query);
}

for (Map.Entry<String, List<String>> entry : uriComponents.getQueryParams().entrySet()) {
for (String value : entry.getValue()) {
servletRequest.addParameter(
UriUtils.decode(entry.getKey(), "UTF-8"),
UriUtils.decode(value, "UTF-8"));
}
}
} catch (UnsupportedEncodingException ex) {
// shouldn't happen
}
return servletRequest;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest req) throws Exception {
handleHttp(ctx, req);
}

private void handleHttp(ChannelHandlerContext ctx, FullHttpRequest req) throws Exception{
if (!req.getDecoderResult().isSuccess()) {
sendError(ctx, BAD_REQUEST);
return;
}
MockHttpServletRequest servletRequest = createServletRequest(req);
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.headers().add(name, value);
}
}

// Write the initial line and the header.
ctx.write(response);

InputStream contentStream = new ByteArrayInputStream(servletResponse.getContentAsByteArray());

// Write the content and flush it.
ChannelFuture writeFuture = ctx.writeAndFlush(new ChunkedStream(contentStream));
writeFuture.addListener(ChannelFutureListener.CLOSE);
}

private static void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) {
ByteBuf content = Unpooled.copiedBuffer(
"Failure: " + status.toString() + "\r\n",
CharsetUtil.UTF_8);

FullHttpResponse fullHttpResponse = new DefaultFullHttpResponse(
HTTP_1_1,
status,
content
);
fullHttpResponse.headers().add(CONTENT_TYPE, "text/plain; charset=UTF-8");

// Close the connection as soon as the error message is sent.
ctx.write(fullHttpResponse).addListener(ChannelFutureListener.CLOSE);
}
}
package com._22evil.web_container;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.stream.ChunkedWriteHandler;
import org.springframework.mock.web.MockServletConfig;
import org.springframework.mock.web.MockServletContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

/**
* Created by Administrator on 2016/12/29.
*/
public class MyNettyInitalizer extends ChannelInitializer<SocketChannel> {
private final DispatcherServlet dispatcherServlet;

public MyNettyInitalizer() throws Exception{
MockServletContext servletContext = new MockServletContext();
MockServletConfig servletConfig = new MockServletConfig(servletContext);

AnnotationConfigWebApplicationContext wac = new AnnotationConfigWebApplicationContext();
wac.setServletContext(servletContext);
wac.setServletConfig(servletConfig);
wac.register(AppConfig.class);
wac.refresh();

this.dispatcherServlet = new DispatcherServlet(wac);
this.dispatcherServlet.init(servletConfig);
}

@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// if (sslCtx != null) {
// pipeline.addLast(sslCtx.newHandler(ch.alloc()));
// }


pipeline.addLast("decoder", new HttpRequestDecoder());
pipeline.addLast("aggregator", new HttpObjectAggregator(65536));
pipeline.addLast("encoder", new HttpResponseEncoder());
pipeline.addLast("chunkedWriter", new ChunkedWriteHandler());
pipeline.addLast("handler", new MyHttpHandler(this.dispatcherServlet));
}
}
package com._22evil.web_container;

/**
* Created by shangguyi on 11/3/16.
* config
*/
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configuration
@EnableWebMvc
@ImportResource({"classpath*:/spring.xml"})
public class AppConfig {
}

global配置,这里由于环境测试的问题增加了很多无用代码

package com._22evil.web_container.global;
import freemarker.template.Configuration;
import freemarker.template.TemplateExceptionHandler;
import java.io.File;
public class Global {
private static volatile Configuration free_marker_cfg;


public static Configuration getFreeMarkerConfig() {
if (null == free_marker_cfg ) {
synchronized(Configuration.class) {
if (null == free_marker_cfg) {
try {
free_marker_cfg = new Configuration(Configuration.VERSION_2_3_28);
File file = new File("template");
if (file.exists()) {
 
} else {
file = new File("");
String path = file.getCanonicalPath();
file = new File(path + "\\src\\main\\resources\\template");
}
free_marker_cfg.setDirectoryForTemplateLoading(file);
free_marker_cfg.setDefaultEncoding("UTF-8");
free_marker_cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
return free_marker_cfg;
}
}

普通url控制,随便写了几个测试

package com._22evil.web_container.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import freemarker.template.Template;

import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.util.Date;
import org.springframework.web.bind.annotation.PathVariable;
import com._22evil.web_container.global.Global;

@Controller("/a")
public class HelloController {

@RequestMapping(value = "abcd", produces = "text/html;charset=UTF-8") //方法1 强制设置为UTF-8,(spring 默认编码不是这个)
public @ResponseBody String abc() {
return "abc你好啊";
}

@RequestMapping("123")
public @ResponseBody String abc1() {
return "a123你好";
}


@RequestMapping("/hello1")
public @ResponseBody String hello() {
return "hello";
}

@RequestMapping("/abc1/{name}")
public @ResponseBody
String abc(@PathVariable String name) {
return name + new Date().toString();
}

@RequestMapping("/a1")
public @ResponseBody
String a(HttpServletRequest request) {
String name = request.getParameter("name");
System.out.println(name);
String str = "hhh heloo ww" + name;
return str;
}

@RequestMapping("/b1")
public @ResponseBody
String b() {
return "this is b";
}

@RequestMapping("/c1")
public @ResponseBody
String c() {
return "this is c";
}

@RequestMapping(value = "/testabc", produces = "text/html;charset=UTF-8")
public void testabc(HttpServletRequest request, HttpServletResponse response) {
try {
System.err.println("-----testabc----");
Map<String, Object> root = new HashMap<>();
root.put("username", "zhujisna");
root.put("age", 25);
// Stu stu = new Stu();
// stu.setAddr("GZ");
// stu.setTel("13467952"); // 方法不可行

Map<String, Object> stu = new HashMap<>();
stu.put("addr", "GZ");
stu.put("tel", "1186459513");
root.put("stu", stu);

response.setCharacterEncoding("UTF-8"); //不加入这句,中文就是?显示
Template temp = Global.getFreeMarkerConfig().getTemplate("testabc.html");
temp.process(root, response.getWriter()); // 此时得到的便是testabc.html内容
 
response.setContentType("text/html; charset=" + temp.getEncoding()); //中文乱码问题


// 测试下sesstion问题, 每次session都会不同,建议用spring-session上传到redis
System.out.println(request.getSession());
Object id = request.getSession().getAttribute("id");
if (id == null) {
request.getSession().setAttribute("id", "1234");
System.out.println("=== session 没有id");
} else {
System.out.println("====session id = " + id);
}

} catch (Exception e) {
e.printStackTrace();
}
}

static class Stu {
String tel;
String addr;

public void setTel(String val) {
this.tel = val;
}

public void setAddr(String val) {
this.addr = val;
}

public String getTel() {
return tel;
}

public String getAddr() {
return addr;
}
}
}


静态文件控制

package com._22evil.web_container.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import java.io.File;
import java.io.FileInputStream;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 处理静态资源
*/
@Controller
public class StaticFileController {

@RequestMapping(value = {"/static/*/*", "/static/*"})
public void solveStatic(HttpServletRequest request, HttpServletResponse response) throws Exception{
// 备注 如果文件是文本类型而且有中文,依旧是乱码
response.setCharacterEncoding("UTF-8");
String url = request.getRequestURI();
 
File file = new File("template/static");
if (file.exists()) {

} else {
file = new File("");
String filePath = file.getCanonicalPath();
file = new File (filePath + "\\src\\main\\resources\\template\\" + url.toString());
if (file.exists()) {
FileInputStream in = new FileInputStream(file);
byte[] data = new byte[4096];
int c;
while ( (c = in.read(data)) > 0) {
response.getOutputStream().write(data, 0, c);
}
in.close();
} else {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
}
}
}
}

至此,已经完成了一个基本的架子。

下面是一些跑的结果

访问api例子

      

访问模板例子 

代码地址: https://github.com/doublEeVil/CommonCoding/tree/master/CommonJava/src/main/java/com/_22evil/web_container

猜你喜欢

转载自www.cnblogs.com/guiyetingfeng/p/9155738.html