手写简单的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 persons ;
private Person person ;
private String tag; //存储操作标签
@Override
public void startDocument() throws SAXException {
persons = new ArrayList();
}
@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 getPersons() {
return persons;
}
}
解析web.xml 文件
文件的解析,用SAX解析,而通过url找到相应的类,就需要创建一个Context类,将集合转化为Map集合,通过键值对的方式找到对应的servlet类
Login
com.yn.server01.LoginServlet
Login
/login
/login02
index
com.yn.server01.IndexServlet
index
/index
封装对应的类
public class Entity {
private String name;
private String clz;
......
}
public class Mapping {
private String name ;
private Set patterns;
public void addPattern(String pattern){
this.patterns.add(pattern);
}
......
}
创建webContext类,将list集合转化为Map集合
public class WebContext {
private Map entityMap=new HashMap();
private Map mappingMap=new HashMap();
private List entitys=null;
private List mappings=null;
public WebContext(List entitys, List 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 entitys = handler.getEntitys();
List 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 entitys ;
private List mappings ;
private Entity entity;
private Mapping mapping;
private boolean isMapping=false ;
private String tag; //
@Override
public void startDocument() throws SAXException {
entitys =new ArrayList();
mappings =new ArrayList();
}
@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 getEntitys() {
return entitys;
}
public List 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(){
}
}
获取响应协议
动态的添加内容
累加字节数的长度
根据状态码拼接响应头协议 (注意空格和换行)
使用输出流输出
根据状态码同一推送出去
//获取响应协议
StringBuilder content =new StringBuilder();
content.append("");
content.append("");
content.append("");
content.append("服务器响应成功");
content.append(" ");
content.append("");
content.append("");
content.append("shsxt server终于回来了。。。。");
content.append("");
content.append("");
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
构建响应行和响应头
响应行: HTTP/1.1 200 OK
响应行中要对状态码进行判断,回应不同的信息
响应头正常添加
响应体
响应体要动态添加,同时字节码长度也要添加
将相应推送出去
用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请求的时候,参数列表在请求头和请求体中都有
将获取的请求参数字符串分割加入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> parameterMap;
private final String CRLF = "\r\n";
public Request02(InputStream is){
parameterMap =new HashMap>();
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());
}
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 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 entitys ;
private List mappings ;
private Entity entity;
private Mapping mapping;
private boolean isMapping=false ;
private String tag; //瀛樺偍鎿嶄綔鏍囩
@Override
public void startDocument() throws SAXException {
entitys =new ArrayList();
mappings =new ArrayList();
}
@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 getEntitys() {
return entitys;
}
public List getMappings() {
return mappings;
}
}
WebContext类
public class WebContext {
private Map entityMap=new HashMap();
private Map mappingMap=new HashMap();
private List entitys=null;
private List mappings=null;
public WebContext(List entitys, List 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();
}
}
@Override
public void run() {
try {
if(null== request.getUrl() || request.getUrl().equals("")) {
InputStream is =Thread.currentThread().getContextClassLoader().getResourceAsStream("index.html");
byte b[] = new byte[1024];
int len = 0;
int temp = 0; // 所有读取的内容都使用temp接收
while ((temp = is.read()) != -1) { // 当没有读取完时,继续读取
b[len] = (byte) temp;
len++;
}
System.out.println(new String(b, 0, len));
response.print((new String(b,0,len)));
response.pushToBrowser(200);
is.close();
return ;
}
Servlet servlet= WebApp.getServletFromUrl(request.getUrl());
if(null!=servlet) {
servlet.service(request, response);
//关注了状态码
response.pushToBrowser(200);
}else {
//错误....
InputStream is =Thread.currentThread().getContextClassLoader().getResourceAsStream("error.html");
byte b[] = new byte[1024];
int len = 0;
int temp = 0; // 所有读取的内容都使用temp接收
while ((temp = is.read()) != -1) { // 当没有读取完时,继续读取
b[len] = (byte) temp;
len++;
}
response.print((new String(b,0,len)));
int read = is.read();
response.pushToBrowser(404);
is.close();
}
}catch(Exception e) {
try {
response.println("你好我不好,我会马上好");
response.pushToBrowser(500);
} catch (IOException e1) {
e1.printStackTrace();
}
}
release();
}
//释放资源
private void release() {
try {
client.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
做了这个小项目,让我对服务器有了更深的理解。
request和response是将请求和响应的信息分别封装成了一个类。
为什么能从配置文件中找到对应的servlet。用的是xml解析,并将解析的数据封装到对应的类中去。