java nio手动实现简单的http服务器
需求分析
最近在学习HTTP协议,还是希望动手去做一做,所以就自己实现了一个http服务器,主要功能是将http请求封装httpRequest,通过解析web.xml,用不同的handler处理不同的uri,然后再将封装好的httpResponse还原成http响应返回浏览器。
代码已经成功上传至 GitHub
如果对你学习JavaNIO有帮助的话,记得给个star哦!
https://github.com/hansiming/HttpServerByJavaNIO
代码
使用java nio实现监听,完成服务器监听线程
package com.cszjo.com.http.server; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Set; import org.apache.log4j.Logger; import com.cszjo.com.http.handler.HttpHandler; import com.cszjo.com.http.utils.XMLUtil; /** * @Title: Server.java * @Description: 打开服务 * @author: Han * @date: 2016年7月12日 下午7:22:47 */ public class Server implements Runnable { private boolean interrupted = false; private Logger logger = Logger.getLogger(Server.class); public Server(boolean interrupted) { this.interrupted = interrupted; } @Override public void run() { try { //打开一个选择器 Selector selector = Selector.open(); //打开ServerSocketChannel通道 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); //得到ServerSocket对象 ServerSocket serverSocket = serverSocketChannel.socket(); //ServerSocketChannel通道监听server.xml中设置的端口 String portStr = XMLUtil.getRootElement("server.xml").element("port").getText(); serverSocket.setReuseAddress(true); try { serverSocket.bind(new InetSocketAddress(Integer.parseInt(portStr))); } catch (Exception e) { logger.error("绑定端口失败,请检查server.xml中是否设置了port属性"); return; } logger.info("成功绑定端口" + portStr); //将通道设置为非阻塞模式 serverSocketChannel.configureBlocking(false); //将serverSocketChannel注册给选择器,并绑定ACCEPT事件 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); logger.info("服务器启动成功"); while(!interrupted) { //查询就绪的通道数量 int readyChannels = selector.select(); //没有就绪的则继续进行循环 if(readyChannels == 0) continue; //获得就绪的selectionKey的set集合 Set<SelectionKey> keys = selector.selectedKeys(); //获得set集合的迭代器 Iterator<SelectionKey> iterator = keys.iterator(); while(iterator.hasNext()) { SelectionKey key = iterator.next(); if(key.isAcceptable()) { //该key有ACCEPT事件 //将监听得到的channel强转为ServerSocketChannel ServerSocketChannel server = (ServerSocketChannel) key.channel(); //得到接收到的SocketChannel SocketChannel socketChannel = server.accept(); if(socketChannel != null) { logger.info("收到了来自" + ((InetSocketAddress)socketChannel.getRemoteAddress()).getHostString() + "的请求"); //将socketChannel设置为阻塞模式 socketChannel.configureBlocking(false); //将socketChannel注册到选择器 socketChannel.register(selector, SelectionKey.OP_READ); } } else if (key.isReadable()) { //该key有Read事件 SocketChannel socketChannel = (SocketChannel) key.channel(); String requestHeader = ""; //拿出通道中的Http头请求 try { requestHeader = receive(socketChannel); } catch (Exception e) { logger.error("读取socketChannel出错"); return; } //启动线程处理该请求,if条件判断一下,防止心跳包 if(requestHeader.length() > 0) { logger.info("该请求的头格式为 " + requestHeader); logger.info("启动了子线程.."); new Thread(new HttpHandler(requestHeader, key)).start(); } } else if (key.isWritable()) { //该key有Write事件 logger.info("有流写出!"); SocketChannel socketChannel = (SocketChannel) key.channel(); socketChannel.shutdownInput(); socketChannel.close(); } //从key集合中删除key,这一步很重要,就是因为没写这句,Selector.select()方法一直返回的是0 //原因分析可能是不从集合中删除,就不会回到I/O就绪事件中 iterator.remove(); } } } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } private String receive(SocketChannel socketChannel) throws Exception { //声明一个1024大小的缓冲区 ByteBuffer buffer = ByteBuffer.allocate(1024); byte[] bytes = null; int size = 0; //定义一个字节数组输出流 ByteArrayOutputStream baos = new ByteArrayOutputStream(); //将socketChannel中的数据写入到buffer中,此时的buffer为写模式,size为写了多少个字节 while ((size = socketChannel.read(buffer)) > 0) { //将写模式改为读模式 //The limit is set to the current position and then the position is set to zero. //将limit设置为之前的position,而将position置为0,更多java nio的知识会写成博客的 buffer.flip(); bytes = new byte[size]; //将Buffer写入到字节数组中 buffer.get(bytes); //将字节数组写入到字节缓冲流中 baos.write(bytes); //清空缓冲区 buffer.clear(); } //将流转回字节数组 bytes = baos.toByteArray(); return new String(bytes); } }
实现MapHandler,解析web.xml,完成uri到对应handler的映射,该handler使用单例完成
package com.cszjo.com.http.handler; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.log4j.Logger; import org.dom4j.Element; import com.cszjo.com.http.utils.XMLUtil; /** * @Title: HandlerMap.java * @Description: HandlerMap(单例) 访问路径--->相应解决类 * @author: Han * @date: 2016年7月15日 下午4:52:29 */ public class MapHandler { //访问路径对应控制类 private static Map<String, Handler> handlerMap = new HashMap<>(); private static MapHandler instance = null; //将构造器私有化 private MapHandler(){} //得到HandlerMap对象实例 public static MapHandler getContextMapInstance() { if(instance == null) { synchronized (MapHandler.class) { if(instance == null) { instance = new MapHandler(); //得到web.xml的根路径 Element rootElement = XMLUtil.getRootElement("web.xml"); //得到handler的集合 List<Element> handlers = XMLUtil.getElements(rootElement); for (Element element : handlers) { Element urlPattenEle = XMLUtil.getElement(element, "url-patten"); //得到urlPatten(uri) String urlPatten = XMLUtil.getElementText(urlPattenEle); Element handlerClazzEle = XMLUtil.getElement(element, "handler-class"); //得到handler 的class文件路径 String clazzPath = XMLUtil.getElementText(handlerClazzEle); Class<?> clazz = null; try { //通过反射得到handler实例化对象,然后以键值对的形式存储 clazz = Class.forName(clazzPath); Handler handler = (Handler)clazz.newInstance(); instance.getHandlerMap().put(urlPatten, handler); Logger.getLogger(MapHandler.class).info("成功添加Handler " + clazzPath); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } } } } return instance; } public Map<String, Handler> getHandlerMap() { return handlerMap; } }
web.xml
<?xml version="1.0" encoding="UTF-8"?> <server> <handler> <handler-class>com.cszjo.com.http.handler.impl.LogionHandler</handler-class> <url-patten>/login</url-patten> </handler> </server>
httpHandler,处理一次http请求,通过uri启动不同的handler进行处理
package com.cszjo.com.http.handler; import java.nio.channels.SelectionKey; import org.apache.log4j.Logger; import com.cszjo.com.http.context.Context; import com.cszjo.com.http.context.impl.HttpContext; import com.cszjo.com.http.handler.impl.NotFoundHandler; /** * @Title: HandlerHttp.java * @Description: 处理一次Http请求 * @author: Han * @date: 2016年7月15日 下午7:07:21 */ public class HttpHandler implements Runnable { //就绪的I/O键 private SelectionKey key; //上下文 private Context context = new HttpContext(); //http请求字符串 private String requestHeader; //针对uri选择不同的处理器 private Handler handler; private Logger logger = Logger.getLogger(HttpHandler.class); public HttpHandler(String requestHeader, SelectionKey key) { this.key = key; this.requestHeader = requestHeader; } @Override public void run() { //初始化上下文 context.setContext(requestHeader, key); //得到uri String uri = context.getRequest().getUri(); logger.info("得到了uri " + uri); //得到MapHandler集合(uri-->handler) handler = MapHandler.getContextMapInstance().getHandlerMap().get(uri); //找不到对应的handler if(handler == null) { //404Handler进行处理 handler = new NotFoundHandler(); } //初始化handler并执行 handler.init(context); } }
Context上下文抽象类设计
package com.cszjo.com.http.context; import java.nio.channels.SelectionKey; /** * @Title: Context.java * @Description: Http上下文抽象类 * @author: Han * @date: 2016年7月16日 下午2:19:06 */ public abstract class Context { protected Request request; protected Response response; /** * 设置当前连接的上下文 * @param: @return * @return: Context * @Autor: Han */ public abstract void setContext(String requestHeader, SelectionKey key); /** * 得到Request * @param: @return * @return: Request * @Autor: Han */ public Request getRequest() { return request; } /** * 得到Response * @param: @return * @return: Response * @Autor: Han */ public Response getResponse() { return response; } }
HttpContext的实现
package com.cszjo.com.http.context.impl; import java.nio.channels.SelectionKey; import com.cszjo.com.http.context.Context; import com.cszjo.com.http.context.Request; import com.cszjo.com.http.context.Response; /** * @Title: HttpContext.java * @Description: HttpContext http上下文 * @author: Han * @date: 2016年7月16日 下午2:20:00 */ public class HttpContext extends Context { private Request request; private Response response; @Override public void setContext(String requestHeader, SelectionKey key) { //初始化request request = new HttpRequest(requestHeader); //初始化response response = new HttpResponse(key); setRequest(); setResponse(); } private void setRequest() { super.request = this.request; } private void setResponse() { super.response = this.response; } }
Request接口设计
package com.cszjo.com.http.context; import java.util.Map; import java.util.Set; /** * @Title: Request.java * @Description: 接口设计:Request接口 * @author: Han * @date: 2016年7月15日 下午9:21:45 */ public interface Request { public static final String POST = "POST"; public static final String GET = "GET"; /** * 得到参数 * @param: @return * @return: Map<String,Object> * @Autor: Han */ public Map<String, Object> getAttribute(); /** * 得到请求方式 * @param: @return * @return: String * @Autor: Han */ public String getMethod(); /** * 得到URI * @param: @return * @return: String * @Autor: Han */ public String getUri(); /** * 版本协议 * @param: @return * @return: String * @Autor: Han */ public String getProtocol(); /** * 得到请求头Map * @param: @return * @return: String * @Autor: Han */ public Map<String, Object> getHeaders(); /** * 得到请求头参数集合 * @param: @return * @return: String * @Autor: Han */ public Set<String> getHeaderNames(); /** * 根据请求头名得到对应的请求头 * @param: @return * @return: String * @Autor: Han */ public Object getHeader(String key); }
HttpRequest实现
package com.cszjo.com.http.context.impl; import java.util.HashMap; import java.util.Map; import java.util.Set; import com.cszjo.com.http.context.Request; /** * @Title: HttpRequest.java * @Description: HTTP请求(还有很多方法可以写的) * @author: Han * @date: 2016年7月15日 下午9:16:45 */ public class HttpRequest implements Request { //参数 private Map<String, Object> attribute = new HashMap<>(); //请求头(Request Header) private Map<String, Object> headers = new HashMap<>(); //请求方法 private String method; //uri private String uri; //协议版本 private String protocol; public HttpRequest(String httpHeader) { init(httpHeader); } private void init(String httpHeader) { //将请求分行 String[] headers = httpHeader.split(" "); //设置请求方式 initMethod(headers[0]); //设置URI initURI(headers[0]); //设置版本协议 initProtocol(headers[0]); //设置请求头 initRequestHeaders(headers); } /** * 设置请求方法 * @param: @param str * @return: void * @Autor: Han */ private void initMethod(String str) { method = str.substring(0, str.indexOf(" ")); } /** * 设置request参数 * @param: @param attr * @return: void * @Autor: Han */ private void initAttribute(String attr) { String[] attrs = attr.split("&"); for (String string : attrs) { String key = string.substring(0, string.indexOf("=")); String value = string.substring(string.indexOf("=") + 1); attribute.put(key, value); } } /** * 设置uri * @param: @param str * @return: void * @Autor: Han */ private void initURI(String str) { uri = str.substring(str.indexOf(" ") + 1, str.indexOf(" ", str.indexOf(" ") + 1)); //如果是get方法,则后面跟着参数 /index?a=1&b=2 if(method.toUpperCase().equals("GET")) { //有问号表示后面跟有参数 if(uri.contains("?")) { String attr = uri.substring(uri.indexOf("?") + 1, uri.length()); uri = uri.substring(0, uri.indexOf("?")); initAttribute(attr); } } } /** * 初始化请求头 * @param: @param strs * @return: void * @Autor: Han */ private void initRequestHeaders(String[] strs) { //去掉第一行 for(int i = 1; i < strs.length; i++) { String key = strs[i].substring(0, strs[i].indexOf(":")); String value = strs[i].substring(strs[i].indexOf(":") + 1); headers.put(key, value); } } /** * 设置协议版本 * @param: @param str * @return: void * @Autor: Han */ private void initProtocol(String str) { protocol = str.substring(str.lastIndexOf(" ") + 1, str.length()); } @Override public Map<String, Object> getAttribute() { return attribute; } @Override public String getMethod() { return method; } @Override public String getUri() { return uri; } @Override public String getProtocol() { return protocol; } @Override public Map<String, Object> getHeaders() { return headers; } @Override public Set<String> getHeaderNames() { return headers.keySet(); } @Override public Object getHeader(String key) { return headers.get(key); } }
Response接口设计
package com.cszjo.com.http.context; import java.nio.channels.SelectionKey; import com.cszjo.com.http.utils.XMLUtil; /** * @Title: Response.java * @Description: 接口设计:response接口 * @author: Han * @date: 2016年7月16日 下午2:19:25 */ public interface Response { //服务器名字 public static final String SERVER_NAME = XMLUtil.getRootElement("server.xml").element("serverName").getText(); public String getContentType(); public int getStatuCode(); public String getStatuCodeStr(); public String getHtmlFile(); public void setHtmlFile(String htmlFile); public SelectionKey getKey(); public void setContentType(String contentType); public void setStatuCode(int statuCode); public void setStatuCodeStr(String statuCodeStr); }
httpResponse实现
package com.cszjo.com.http.context.impl; import java.nio.channels.SelectionKey; import com.cszjo.com.http.context.Response; /** * @Title: HttpResponse.java * @Description: http响应 * @author: Han * @date: 2016年7月16日 下午2:20:41 */ public class HttpResponse implements Response { private SelectionKey key; //内容类型 defalut 为text/html private String contentType = "text/html"; //响应码 defalut 为200 private int StatuCode = 200; private String statuCodeStr = "OK"; private String htmlFile = ""; public HttpResponse(SelectionKey key) { this.key = key; } @Override public String getContentType() { return contentType; } @Override public int getStatuCode() { return StatuCode; } @Override public SelectionKey getKey() { return key; } @Override public String getStatuCodeStr() { return statuCodeStr; } @Override public String getHtmlFile() { return htmlFile; } @Override public void setHtmlFile(String htmlFile) { this.htmlFile = htmlFile; } @Override public void setContentType(String contentType) { this.contentType = contentType; } @Override public void setStatuCode(int statuCode) { StatuCode = statuCode; } @Override public void setStatuCodeStr(String statuCodeStr) { this.statuCodeStr = statuCodeStr; } }
处理器Handler的接口设计
package com.cszjo.com.http.handler; import com.cszjo.com.http.context.Context; /** * @Title: Handler.java * @Description: 接口设计:处理器Handler接口 * @author: Han * @date: 2016年7月12日 下午7:12:37 */ public interface Handler { /** * 初始化handler * @param: @param context * @return: void * @Autor: Han */ public void init(Context context); /** * handler service(service应该不是这样做的... - -!) * @param: @param context * @return: void * @Autor: Han */ public void service(Context context); /** * Get形式执行该方法 * @param: @param context * @return: void * @Autor: Han */ public void doGet(Context context); /** * POST形式执行该方法 * @param: @param context * @return: void * @Autor: Han */ public void doPost(Context context); /** * 销毁Handler(并没有销毁... - -!) * @param: @param context * @return: void * @Autor: Han */ public void destory(Context context); }
因为doGet或者doPost只会执行一个,所以中间在写一个抽象类,具体的handler只需要重写该抽象类的方法既可
package com.cszjo.com.http.handler.abs; import com.cszjo.com.http.context.Context; import com.cszjo.com.http.context.Request; import com.cszjo.com.http.handler.Handler; import com.cszjo.com.http.handler.ResponseHandler; /** * @Title: AbstractHandler.java * @Description: Handler抽象类 * @author: Han * @date: 2016年7月16日 下午2:11:57 */ public class AbstractHandler implements Handler { protected Context context; @Override public void init(Context context) { this.context = context; this.service(context); } @Override public void service(Context context) { //通过请求方式选择具体解决方法 String method = context.getRequest().getMethod(); if(method.equals(Request.GET)) { this.doGet(context); } else if (method.equals(Request.POST)) { this.doPost(context); } sendResponse(context); } @Override public void doGet(Context context) { } @Override public void doPost(Context context) { } @Override public void destory(Context context) { context = null; } /** * 通过上下文,返回封装response响应 * @param: @param context * @return: void * @Autor: Han */ private void sendResponse(Context context) { new ResponseHandler().write(context); } }
Login Handler的实现
package com.cszjo.com.http.handler.impl; import org.apache.log4j.Logger; import com.cszjo.com.http.context.Context; import com.cszjo.com.http.handler.abs.AbstractHandler; /** * @Title: LogionHandler.java * @Description: 解决login业务逻辑 * @author: Han * @date: 2016年7月16日 下午2:08:18 */ public class LogionHandler extends AbstractHandler{ private Logger logger = Logger.getLogger(LogionHandler.class); @Override public void doGet(Context context) { logger.info("进入了handler--->LoginHandler"); context.getResponse().setHtmlFile("login.html"); } }
未找到请求的URI,所以返回404,该处理器处理404错误
package com.cszjo.com.http.handler.impl; import org.apache.log4j.Logger; import com.cszjo.com.http.context.Context; import com.cszjo.com.http.context.Response; import com.cszjo.com.http.handler.abs.AbstractHandler; /** * @Title: NotFoundHandler.java * @Description: 解决404NotFound响应 * @author: Han * @date: 2016年7月16日 下午2:08:44 */ public class NotFoundHandler extends AbstractHandler { private Logger logger = Logger.getLogger(NotFoundHandler.class); private Response response; @Override public void doGet(Context context) { logger.info("进入了404Handler"); response = context.getResponse(); response.setStatuCode(404); response.setStatuCodeStr("Not Found"); response.setHtmlFile("404.html"); } }
封装完http请求,下一步就需要还原response响应了
package com.cszjo.com.http.handler; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.util.Date; import org.apache.log4j.Logger; import com.cszjo.com.http.context.Context; import com.cszjo.com.http.context.Request; import com.cszjo.com.http.context.Response; /** * @Title: ResponseHandler.java * @Description: 封装response响应 * @author: Han * @date: 2016年7月16日 下午2:09:45 */ public class ResponseHandler { private Request request; private Response response; private String protocol; private int statuCode; private String statuCodeStr; private ByteBuffer buffer; private String serverName; private String contentType; private SocketChannel channel; private Selector selector; private SelectionKey key; private Logger logger = Logger.getLogger(ResponseHandler.class); private BufferedReader reader; private String htmlFile; public void write(Context context) { //从context中得到相应的参数 request = context.getRequest(); response = context.getResponse(); buffer = ByteBuffer.allocate(1024); protocol = request.getProtocol(); statuCode = response.getStatuCode(); statuCodeStr = response.getStatuCodeStr(); serverName = Response.SERVER_NAME; contentType = response.getContentType(); key = response.getKey(); selector = key.selector(); channel = (SocketChannel)key.channel(); htmlFile = response.getHtmlFile(); //得到响应正文内容 String html = setHtml(context); StringBuilder sb = new StringBuilder(); //状态行 sb.append(protocol + " " + statuCode + " " + statuCodeStr + " "); //响应头 sb.append("Server: " + serverName + " "); sb.append("Content-Type: " + contentType + " "); sb.append("Date: " + new Date() + " "); if(reader != null) { sb.append("Content-Length: " + html.getBytes().length + " "); } //响应内容 sb.append(" "); sb.append(html); buffer.put(sb.toString().getBytes()); //从写模式,切换到读模式 buffer.flip(); try { logger.info("生成相应 " + sb.toString()); channel.register(selector, SelectionKey.OP_WRITE); channel.write(buffer); } catch (IOException e) { e.printStackTrace(); } } private String setHtml(Context context) { StringBuilder html = null; if(htmlFile != null && htmlFile.length() > 0) { html = new StringBuilder(); try { reader = new BufferedReader(new FileReader(new File(htmlFile))); String htmlStr; htmlStr = reader.readLine(); while(htmlStr != null) { html.append(htmlStr + " "); htmlStr = reader.readLine(); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } return html.toString(); } }
程序启动入口
package com.cszjo.com.http.server; /** * @Title: Solution.java * @Description: 启动Web服务器入口 * @author: Han * @date: 2016年7月12日 下午7:11:15 */ public class Solution { //启动方法 public static void main(String[] args) { new Thread(new Server(false)).start(); } }
XMLUtils
package com.cszjo.com.http.utils; import java.io.File; import java.util.List; import org.apache.log4j.Logger; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; /** * @Title: XMLUtil.java * @Description: 解决XML读取问题 * @author: Han * @date: 2016年7月15日 下午4:55:28 */ public class XMLUtil { private static Logger logger = Logger.getLogger(XMLUtil.class); private static SAXReader reader = new SAXReader(); /** * 得到根节点 * @param: @param xmlPath * @param: @return * @return: Element * @Autor: Han */ public static Element getRootElement(String xmlPath) { Document document = null;; try { document = reader.read(new File(xmlPath)); } catch (DocumentException e) { logger.error("找不到指定的xml文件的路径" + xmlPath + "!"); return null; } return document.getRootElement(); } /** * 得到该节点下的子节点集合 * @param: @param element * @param: @return * @return: List<Element> * @Autor: Han */ @SuppressWarnings("unchecked") public static List<Element> getElements(Element element) { return element.elements(); } /** * 得到该节点下指定的节点 * @param: @param name * @param: @return * @return: Element * @Autor: Han */ public static Element getElement(Element element, String name) { Element childElement = element.element(name); if(childElement == null) { logger.error(element.getName() + "节点下没有子节点" + name); return null; } return childElement; } /** * 得到该节点的内容 * @param: @param element * @param: @return * @return: String * @Autor: Han */ public static String getElementText(Element element) { return element.getText(); } }
该项目需要用到的两个包:log4j,dom4j
其余配置文件和静态文件
log4j.properties
### u8BBEu7F6E###
log4j.rootLogger = debug,stdout,D,E
### u8F93u51FAu4FE1u606Fu5230u63A7u5236u62AC ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
### u8F93u51FADEBUG u7EA7u522Bu4EE5u4E0Au7684u65E5u5FD7u5230=E://logs/error.log ###
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = E://logs/log.log
log4j.appender.D.Append = true
log4j.appender.D.Threshold = DEBUG
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
### u8F93u51FAERROR u7EA7u522Bu4EE5u4E0Au7684u65E5u5FD7u5230=E://logs/error.log ###
log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
log4j.appender.E.File =E://logs/error.log
log4j.appender.E.Append = true
log4j.appender.E.Threshold = ERROR
log4j.appender.E.layout = org.apache.log4j.PatternLayout
log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
server.xml
<?xml version="1.0" encoding="UTF-8"?> <server> <port>8089</port> <serverName>Han`s Server</serverName> <!-- 默认编码为UTF-8 --> <charset>UTF-8</charset> </server>
login.html
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="Generator" content="EditPlus®"> <meta name="Author" content=""> <meta name="Keywords" content=""> <meta name="Description" content=""> <title>han登录</title> </head> <body> 用户名:<input type="text" name="userName"><br/> 密码:<input type="text" name="userName"><br/> </body> </html>
404.html
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="Generator" content="EditPlus®"> <meta name="Author" content=""> <meta name="Keywords" content=""> <meta name="Description" content=""> <title>Document</title> </head> <body> <h1>404 NOT Found</h1> <strong style="color:red;">来自Han服务器</strong> </body> </html>
测试
启动服务
在浏览器中输入http://localhost:8089/login之后
浏览器显示
在浏览器中输入http://localhost:8089/lo之后
OK,成功!