手写简单的web服务器
一、用到的知识
oop,容器,io,多线程,网络编程,xml解析,反射,HTML,http
1.反射
将Java类中的各种结构映射成一个个Java对象,利用反射对一个类进行解剖,反射是框架设计灵魂
jdk9 用反射创建对象不再用.newInstance()创建对象,而是getConstructor().newInstance();
clz = Class.forName("包名.类名");
//创建对象
clz.newInstance();//9,不再这样用
clz.getConstructor().newInstance();
2.xml解析
XML 可扩展标记语言 树结构
这里用SAX解析
{ //1、获取解析工厂
SAXParserFactory factory=SAXParserFactory.newInstance();
//2、从解析工厂获取解析器
SAXParser parse =factory.newSAXParser();
//3、编写处理器
//4、加载文档 Document 注册处理器
PHandler handler=new PHandler();
//5、解析
parse.parse(Thread.currentThread().getContextClassLoader()
.getResourceAsStream("com/sxt/server/basic/p.xml")
,handler);
}
class PersonHandler extends DefaultHandler{
private List<Person> persons ;
private Person person ;
private String tag; //存储操作标签
@Override
public void startDocument() throws SAXException {
persons = new ArrayList<Person>();
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if(null!=qName) {
tag = qName; //存储标签名
if(tag.equals("person")) {
person = new Person();
}
}
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
String contents = new String(ch,start,length).trim();
if(null!=tag) { //处理了空
if(tag.equals("name")) {
person.setName(contents);
}else if(tag.equals("age")) {
if(contents.length()>0) {
person.setAge(Integer.valueOf(contents));
}
}
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if(null!=qName) {
if(qName.equals("person")) {
persons.add(person);
}
}
tag = null; //tag丢弃了
}
@Override
public void endDocument() throws SAXException {
}
public List<Person> getPersons() {
return persons;
}
}
解析web.xml 文件
文件的解析,用SAX解析,而通过url找到相应的类,就需要创建一个Context类,将集合转化为Map集合,通过键值对的方式找到对应的servlet类
<web-app>
<servlet>
<servlet-name>Login</servlet-name>
<servlet-class>com.yn.server01.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Login</servlet-name>
<url-pattern>/login</url-pattern>
<url-pattern>/login02</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>index</servlet-name>
<servlet-class>com.yn.server01.IndexServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>index</servlet-name>
<url-pattern>/index</url-pattern>
</servlet-mapping>
</web-app>
封装对应的类
public class Entity {
private String name;
private String clz;
......
}
public class Mapping {
private String name ;
private Set<String > patterns;
public void addPattern(String pattern){
this.patterns.add(pattern);
}
......
}
创建webContext类,将list集合转化为Map集合
public class WebContext {
private Map<String,String > entityMap=new HashMap<String,String>();
private Map<String,String> mappingMap=new HashMap<String,String>();
private List<Entity> entitys=null;
private List<Mapping> mappings=null;
public WebContext(List<Entity> entitys, List<Mapping> mappings) {
this.entitys = entitys;
this.mappings = mappings;
//将entity集合转化为Map集合
for (Entity entity : entitys) {
entityMap.put(entity.getName(), entity.getClz());
}
//将mapping集合转化为Map集合
for (Mapping mapping : mappings) {
for (String pattern : mapping.getPatterns()) {
mappingMap.put(pattern, mapping.getName());
}
}
}
/**
* 通过url路径找到对应的class文件
* @param pattern
* @return
*/
public String getClz(String pattern){
String name=mappingMap.get(pattern);
return entityMap.get(name);
}
}
测试类
public class XmlTest02 {
public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
//1、获取解析工厂
SAXParserFactory factory=SAXParserFactory.newInstance();
//2、从解析工厂获取解析器
SAXParser parse =factory.newSAXParser();
//3、编写处理器
//4、加载文档 Document 注册处理器
PHandler handler=new PHandler();
//5、解析
parse.parse(Thread.currentThread().getContextClassLoader()
.getResourceAsStream("com/yn/server01/web.xml")
,handler);
//获取解析后的list集合
List<Entity> entitys = handler.getEntitys();
List<Mapping> mappings = handler.getMappings();
//将list集合转化为Map集合
WebContext wc=new WebContext(entitys, mappings);
//通过url找到对应的class类名
String clz = wc.getClz("/login02");
try {
Class<?> className = Class.forName(clz);
Servlet s = (Servlet) className.newInstance();
s.service();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
class WebHandler extends DefaultHandler{
private List<Entity> entitys ;
private List<Mapping> mappings ;
private Entity entity;
private Mapping mapping;
private boolean isMapping=false ;
private String tag; //
@Override
public void startDocument() throws SAXException {
entitys =new ArrayList<Entity>();
mappings =new ArrayList<Mapping>();
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if(null!=qName) {
tag = qName; //
if(tag.equals("servlet")) {
entity = new Entity();
isMapping = false;
}else if (tag.equals("servlet-mapping")) {
mapping=new Mapping();
isMapping=true;
}
}
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
String contents = new String(ch,start,length).trim();
if(null!=tag) { //处理空
if (isMapping) {//操作servlet-mapping
if(tag.equals("servlet-name")) { mapping.setName(contents); }else if(tag.equals("url-pattern")) { if(contents.length()>0) { mapping.addPattern(contents); } } }else{ if(tag.equals("servlet-name")) { entity.setName(contents); }else if(tag.equals("servlet-class")) { if(contents.length()>0) { entity.setClz(contents); } } } } } @Override public void endElement(String uri, String localName, String qName) throws SAXException { if(null!=qName) { if(qName.equals("servlet")) { entitys.add(entity); }else if(qName.equals("servlet-mapping")) { mappings.add(mapping); } } tag = null; //tag丢弃了 } public List<Entity> getEntitys() { return entitys; } public List<Mapping> getMappings() { return mappings; } }
3.HTML
超文本标记语言
post和get请求的区别,
post 提交 基于http协议不同,量大,参数不可见,安全
get 默认 获取,基于http协议不同,量小,参数可见,不安全
表单的name属性是提供给后台使用的,id作为前端使用的
4.Http协议
超文本传输协议,所有的www文件都必须遵守这个标准
http是应用层的协议 tcp和udp是传输层的协议
请求协议
-
请求行
-
请求头
-
请求正文
相应协议
1.状态行
-
响应头
-
响应正文
二、手写服务器
客户端就不用写了,就是浏览器,现在要写的是服务器的内容
1.获取请求协议
使用ServerSocket获取请求协议
/**
* 使用ServerSocket建立与浏览器的连接,获取请求协议
* @author student
*
*/
public class Server01 {
private ServerSocket ss;
public static void main(String[] args) {
Server01 s=new Server01();
s.start();
}
//启动服务
public void start(){
try {
ss=new ServerSocket(8888);
System.out.println("服务器启动了");
receive();
} catch (IOException e) {
e.printStackTrace();
System.out.println("服务器启动失败");
}
}
//接受连接
public void receive(){
try {
Socket client = ss.accept();
System.out.println("一个客户端建立了连接");
//获取请求协议
InputStream is = client.getInputStream();
byte[] datas=new byte[1024*1024];
int len = is.read(datas);
String requstInfo=new String(datas,0,len);
System.out.println(requstInfo);
} catch (IOException e) {
e.printStackTrace();
System.out.println("客户端连接出现错误");
}
}
//停止服务
public void stop(){
}
}
2. 获取响应协议
动态的添加内容
累加字节数的长度
根据状态码拼接响应头协议 (注意空格和换行)
使用输出流输出
根据状态码同一推送出去
//获取响应协议
StringBuilder content =new StringBuilder();
content.append("<html>");
content.append("<head>");
content.append("<title>");
content.append("服务器响应成功");
content.append("</title>");
content.append("</head>");
content.append("<body>");
content.append("shsxt server终于回来了。。。。");
content.append("</body>");
content.append("</html>");
int size = content.toString().getBytes().length; //必须获取字节长度
StringBuilder responseInfo =new StringBuilder();
String blank =" ";
String CRLF = "\r\n";
//返回
//1、响应行: HTTP/1.1 200 OK
responseInfo.append("HTTP/1.1").append(blank);
responseInfo.append(200).append(blank);
responseInfo.append("OK").append(CRLF);
//2、响应头(最后一行存在空行):
/*
Date:Mon,31Dec209904:25:57GMT
Server:shsxt Server/0.0.1;charset=GBK
Content-type:text/html
Content-length:39725426
*/
responseInfo.append("Date:").append(new Date()).append(CRLF);
responseInfo.append("Server:").append("shsxt Server/0.0.1;charset=GBK").append(CRLF);
responseInfo.append("Content-type:text/html").append(CRLF);
responseInfo.append("Content-length:").append(size).append(CRLF);
responseInfo.append(CRLF);
//3、正文
responseInfo.append(content.toString());
//写出到客户端
BufferedWriter bw =new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
bw.write(responseInfo.toString());
bw.flush();
3.封装response
响应分为响应行,响应头,响应体
响应头中要关注状态码200,404,500
响应头不变
响应体自己写
整个Response类整体分四部分
1.初始化
在无参构造器中将协议头和正文初始化,有参构造器可以传入参数客户端或输出流,初始化
BufferedWriter
2. 构建响应行和响应头
`响应行: HTTP/1.1 200 OK`
响应行中要对状态码进行判断,回应不同的信息
响应头正常添加
3. 响应体
响应体要动态添加,同时字节码长度也要添加
4. 将相应推送出去
用BufferedWriter将内容推送出去
public class Response {
private BufferedWriter bw ;
//正文
private StringBuilder content;
//协议头信息
private StringBuilder headInfo ;
private final String BLANK =" ";
private final String CRLF = "\r\n";
private int len;//正文的字节数
private Response(){
content=new StringBuilder();
headInfo=new StringBuilder();
len=0;
}
public Response(Socket client){
this();
try {
bw =new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
} catch (IOException e) {
e.printStackTrace();
headInfo=null;
}
}
public Response(OutputStream os){
this();
bw =new BufferedWriter(new OutputStreamWriter(os));
}
//动态添加内容
public Response print(String info){
content.append(info);
len+=info.getBytes().length;
return this;
}
public Response println(String info){
content.append(info).append(CRLF);
len+=(info+CRLF).getBytes().length;
return this;
}
//推送响应信息
public void pushToBrowser(int code ){
if (null==headInfo) {
code=500;
}
creatHeadInfo(code);
try {
bw.append(headInfo);
bw.append(content);
bw.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
//构建头信息
private void creatHeadInfo(int code){
//1、响应行: HTTP/1.1 200 OK
headInfo.append("HTTP/1.1").append(BLANK);
headInfo.append(code).append(BLANK);
headInfo.append("OK").append(CRLF);
switch (code) {
case 200:
headInfo.append("OK").append(CRLF);
break;
case 404:
headInfo.append("Not Found").append(CRLF);
break; case 500: headInfo.append("Server Error").append(CRLF); break; } //2、响应头(最后一行存在空行): /* Date:Mon,31Dec209904:25:57GMT Server:shsxt Server/0.0.1;charset=GBK Content-type:text/html Content-length:39725426 */ headInfo.append("Date:").append(new Date()).append(CRLF); headInfo.append("Server:").append("shsxt Server/0.0.1;charset=GBK").append(CRLF); headInfo.append("Content-type:text/html").append(CRLF); headInfo.append("Content-length:").append(len).append(CRLF); headInfo.append(CRLF); }}
4.封装request
封装了五个属性 协议信息,请求方式,请求的url,请求参数,存储参数
1.构造器初始化
在构造器中初始化parameterMap ,并获取协议信息,之后分析协议信息
2.解析协议信息
通过字符串的分割获取想要的信息,字符串去空要注意,其中解析参数复杂了一点
参数的提交分为post和get请求
当是get请求的时候只需要分析请求头中的参数列表
当是post请求的时候,参数列表在请求头和请求体中都有
3. 将请求参数信息转化为map 集合
将获取的请求参数字符串分割加入Map集合
在获取参数的时候,参数的值为中文的时候回产生乱码,就需要处理乱码的问题
4.处理乱码
调用java.net.URLDecoder.decode(value,enc)方法来处理乱码
5.代码
public class Request02 {
//协议信息
private String requstInfo;
//请求方式
private String method;
//请求url
private String url;
//请求参数
private String queryStr;
//存储参数
private Map<String ,List<String>> parameterMap;
private final String CRLF = "\r\n";
public Request02(InputStream is){
parameterMap =new HashMap<String ,List<String>>();
byte[] datas=new byte[1024*1024];
int len;
try {
len = is.read(datas);
this.requstInfo=new String(datas,0,len);
System.out.println(requstInfo);
} catch (IOException e) {
e.printStackTrace();
return ;
}
//分解字符串
parseRequestInfo();
}
public Request02(Socket client) throws IOException{
this(client.getInputStream());
}
private void parseRequestInfo(){
System.out.println("-----分解-------");
System.out.println("-----1、获取请求方式 开头第一个/ ------");
this.method=this.requstInfo.substring(0, this.requstInfo.indexOf("/")).toUpperCase();
System.out.println("-----1、获取请求url 开头第一个/到HTTP/------");
System.out.println("-----可能包含的请求参数 前面的为url------");
//获取第一个/
int startIdx=this.requstInfo.indexOf("/")+1;
//获取HTTP/的位置
int endIdx=this.requstInfo.indexOf("HTTP/");
//分割字符串
this.url = this.requstInfo.substring(startIdx, endIdx);
//获取?的位置
int queryIdx = this.url.indexOf("?");
if (queryIdx>=0) {//表示存在请求参数
String [] urlArray=this.url.split("\\?");
this.url=urlArray[0];
queryStr=urlArray[1];
}
System.out.println("-----获取请求参数,如果是GET已经获取,如果是post可能在请求体中------");
this.queryStr=this.queryStr.trim();
this.method=this.method.trim();
if (method.equals("POST")) {
String qstr=this.requstInfo.substring(this.requstInfo.lastIndexOf(CRLF)).trim();
System.out.println("---->"+qstr);
if (null==queryStr) {
queryStr=qstr;
}else {
queryStr+="&"+qstr;
}
} queryStr= null==queryStr?"":queryStr; //this.queryStr=this.queryStr.trim(); //System.out.println("url :"+this.url+" method :"+method +" queryStr :"+queryStr); System.out.println(queryStr); //转成Map convertMap(); } //处理请求参数为Map private void convertMap(){ //分割字符串 String[] keyValues=this.queryStr.split("&"); for (String queryStr : keyValues) { //再次分割字符串 = String[] kv=queryStr.split("="); kv = Arrays.copyOf(kv, 2); //获取key和value String key=kv[0]; String value=kv[1]==null?null:decode(kv[1], "utf-8"); //存放到Map中 if (!parameterMap.containsKey(key)) { parameterMap.put(key, new ArrayList<String>()); } parameterMap.get(key).add(value); } } private String decode(String value,String enc){ try { return java.net.URLDecoder.decode(value,enc); } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } /** * 通过name获取对应的多个值 * @param key * @return */ public String[] getPatameterValues(String key){ List<String> values = this.parameterMap.get(key); if (null==values||values.size()<0) { return null; } return values.toArray(new String[0]); } /** * 通过name获取对应的一个值 * @param key * @return */ public String getPatameter(String key){ String[] values = getPatameterValues(key); return values==null?null:values[0]; } public String getMethod() { return method; } public String getUrl() { return url; } public String getQueryStr() { return queryStr; } }
5.引入Servlet
创建Servlet父类,定义service方法方法需要传入参数Request和Response对象,在子类中的service方法中,写具体的响应内容response.print(),Server类中判断请求地址url,调用对应的servlet,调用service方法,在页面响应
//接受连接
public void receive(){
try {
Socket client = ss.accept();
System.out.println("一个客户端建立了连接");
//获取请求协议
Request request=new Request(client);
//获取响应协议
Response response=new Response(client);
Servlet servlet = null;
if (request.getUrl().equals("login")) {
servlet=new LoginServlet();
}else if (request.getUrl().equals("index")) {
servlet=new IndexServlet();
}
servlet.service(request, response);
response.pushToBrowser(200);
} catch (IOException e) {
e.printStackTrace();
System.out.println("客户端连接出现错误");
}
}
6.整和web.xml 文件
将之前的web.xml解析的代码拿过来,创建WebApp 和 WebHandler类 解析配置文件,定义方法用来通过url获取配置文件的servlet,WebHandler类,是用来将解析的内容封装到对应的类
webApp类
public class WebApp {
private static WebContext wc;
static {
SAXParserFactory factory=SAXParserFactory.newInstance();
//2銆佷粠瑙f瀽宸ュ巶鑾峰彇瑙f瀽鍣�
SAXParser parse;
try {
parse = factory.newSAXParser();
//3銆佺紪鍐欏鐞嗗櫒
//4銆佸姞杞芥枃妗B燚ocument聽娉ㄥ唽澶勭悊鍣�
WebHandler handler=new WebHandler();
//5銆佽В鏋�
parse.parse(Thread.currentThread().getContextClassLoader()
.getResourceAsStream("com/yn/server03/web.xml")
,handler);
//将配置文件的信息转化为Map集合
wc=new WebContext(handler.getEntitys(), handler.getMappings());
} catch (Exception e) {
e.printStackTrace();
System.out.println("解析配置文件错误");
}
}
/**
* 通过url获取配置文件的servlet
* @param url
* @return
*/
public static Servlet getServletFromUrl(String url){
String clz = wc.getClz("/"+url);
try {
Class<?> className = Class.forName(clz);
Servlet s = (Servlet) className.newInstance();
return s; } catch (Exception e) { e.printStackTrace(); return null; } }
public class WebHandler extends DefaultHandler{
private List<Entity> entitys ;
private List<Mapping> mappings ;
private Entity entity;
private Mapping mapping;
private boolean isMapping=false ;
private String tag; //瀛樺偍鎿嶄綔鏍囩
WebContext类
public class WebContext {
private Map<String,String > entityMap=new HashMap<String,String>();
private Map<String,String> mappingMap=new HashMap<String,String>();
private List<Entity> entitys=null;
private List<Mapping> mappings=null;
public WebContext(List<Entity> entitys, List<Mapping> mappings) {
this.entitys = entitys;
this.mappings = mappings;
//将entity集合转化为Map集合
for (Entity entity : entitys) {
entityMap.put(entity.getName(), entity.getClz());
}
//将mapping集合转化为Map集合
for (Mapping mapping : mappings) {
for (String pattern : mapping.getPatterns()) {
mappingMap.put(pattern, mapping.getName());
}
}
}
/**
* 通过url路径找到对应的class文件
* @param pattern
* @return
*/
public String getClz(String pattern){
String name=mappingMap.get(pattern);
return entityMap.get(name); }
Server02
//接受连接
public void receive(){
try {
Socket client = ss.accept();
System.out.println("一个客户端建立了连接");
//获取请求协议
Request request=new Request(client);
//获取响应协议
Response response=new Response(client);
Servlet servlet = WebApp.getServletFromUrl(request.getUrl());
if (servlet!=null) {
servlet.service(request, response);
response.pushToBrowser(200);
}else {
//错误
response.pushToBrowser(404);
}
response.pushToBrowser(200);
} catch (IOException e) {
e.printStackTrace();
System.out.println("客户端连接出现错误");
}
}
7.高效分发器.
使用多线程来处理每次请求都需启动服务器的问题
首先创建一个Dispatcher线程,在构造器中初始化客户端,请求和响应协议,如果异常,关闭客户端资源
在run方法中通过url获取的servlet方法获取对应的servlet,调用service方法,并在不同错误的时候推送对应的状态码,最后的方法是释放资源
package com.yn.server.core;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.io.IOException;
import java.nio.file.*;
/**
* 分发器:加入状态内容处理 404 505 及首页
*
* @author yn
*
*/
public class Dispatcher implements Runnable {
private Socket client;
private Request request;
private Response response ;
public Dispatcher(Socket client) {
this.client = client;
try {
//获取请求协议
//获取响应协议
request =new Request(client);
response =new Response(client);
} catch (IOException e) {
e.printStackTrace();
this.release();
}
}