手写服务器步骤
1.获取请求协议
1、创建ServerSocket
2、建立连接获取Socket
3、通过输入流获取请求协议
注意: GET与POST不一致的地方
2.返回响应协议
1、准备内容
2、获取字节数的长度
3、拼接响应协议
注意: 空格与换行
4、使用输出流输出
3.封装响应信息
Response
1、动态添加内容print
2、累加字节数的长度
3、根据状态码拼接响应头协议
4、根据状态码统一推送出去
调用处: 动态调用print +传入状态码推送
4.封装请求信息
Request
通过分解字符串获取method URL和请求参
数 POST请求参数可能在 请求体中还存在
5.处理请求参数
Request
通过Map封装请求参数 两个方法
考虑一个参数多个值和中文
6.引入Servlet (小脚本)
Sevlet
将业务代码解耦到对应的业务类中(具体的
Serlvet)
7.整合配置文件
整合配置文件
根据配置文件动态的读取类名,再进行反
射获取具体的Servlet来处理业务,真正的
以不变应万变
8.封装分发器
Dispatcher
加入了多线程,可以同时处理多个请求,
使用的是短连接
9.404以及首页处理
读取错误、首页内容即可
具体代码:
package com.pzy.server.core;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
- 处理404以及505
- @author H
*/
public class Server {
private ServerSocket serversocket;
private boolean isRunning;
public static void main(String[] args) {
Server server = new Server();
server.start();
server.receive();
}
public void start(){
try {
serversocket = new ServerSocket(8870);
isRunning = true;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println(“服务器启动失败?”);
this.stop();
}
}
public void receive(){
while(isRunning){
try {
Socket socket = serversocket.accept();
Dispatcher dis = new Dispatcher(socket);
new Thread(dis).start();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("接收失败");
}
}
}
public void stop(){
isRunning = false;
try {
this.serversocket.close();
System.out.println("服务器已停止");
} catch (IOException e) {
e.printStackTrace();
}
}
}
请求协议
package com.pzy.server.core;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
- 请求协议
- @author H
/
public class Request {
//客户端发送过来的请求信息
private String requestInfo;
//请求方式
private String meathod;
//请求参数
private String queryStr;
//url
private String url;
//一个存放参数的map容器
private Map<String,List> queryStrMap = new HashMap<String,List>();
public Request(Socket client) throws IOException{
this(client.getInputStream());
}
public Request(InputStream is){
byte datas [] = new byte[10241024*1024];
int len;
try {
len= is.read(datas);
if(len>0){
requestInfo = new String(datas,0,len);
}else{
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
paresRequestInfo();
}
private void paresRequestInfo(){//分解字符串
System.out.println("-------分解-----");
// System.out.println(requestInfo);
int index = requestInfo.indexOf("/");
int endIndex = requestInfo.indexOf(“HTTP/”);
//1.获取请求方式
meathod = requestInfo.substring(0,index).toLowerCase().trim();//从0开始到第一个/
//2.获取url
this.url = requestInfo.substring(index+1,endIndex).trim();
index = url.indexOf("?");
if(index>=0){//表示存在?需要进行切割
String urlsp[] = url.split("\\?");
url = urlsp[0];
queryStr = urlsp[1];
}
//如果请求方式是get则请求参数已经全部获取 而如果请求方式是post 则在请求内容的最后一行 最后一行有回车 可以使用lastindex获得最后一个回车的位置
if(meathod.equals("post")){
String qStr= requestInfo.substring(requestInfo.lastIndexOf("\r\n")).trim();
if(queryStr == null){//如果请求参数等于空 及index中没有? 所以直接使用qstr为他赋值
queryStr = qStr;
}else{//否则就对他进行字符串的连接
if(!qStr.equals("")){
queryStr += "&"+qStr;
}
}
}
queryStr = queryStr==null?"":queryStr;
System.out.println(url+"-->"+meathod+"-->"+queryStr);
this.getMap();
}
private void getMap(){//将参数值存放到map中
//存放参数值 如: aaa=14&bbb=oo&sad=&aaa=pp;
//1.首先通过&将queryStr分割开来
String queryStrs[] = queryStr.split("&");
for(String qStr : queryStrs){
//2.然后通过=分割
String kv[] = qStr.split("=");
kv = Arrays.copyOf(kv, 2);
String key = kv[0];
String value = kv[1]==null?null:this.decoing(kv[1], "utf-8");
if(!queryStrMap.containsKey(key)){
queryStrMap.put(key, new ArrayList<String>());
}
queryStrMap.get(key).add(value);
}
}
public String[] getValues(String key){
List<String> values = queryStrMap.get(key);
if(values==null||values.size()<=0){
return null;
}
return values.toArray(new String[0]);
}
public String getValue(String key){
String s[] = getValues(key);
return s == null?null:s[0];
}
public String getMeathod() {
return meathod;
}
public String getQueryStr() {
return queryStr;
}
public String getUrl() {
return url;
}
private String decoing(String value,String enc){
try {
return java.net.URLDecoder.decode(value,enc);
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}
package com.pzy.server.core;
/**
- 响应协议
*/
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.Date;
public class Response {
private BufferedWriter bw;
//正文
StringBuilder content;
//头信息
StringBuilder headInfo;
//字节数组长度
private int len;
//空格和回车
private String blank = " ";
private String CRLF = “\r\n”;
public 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) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public Response(OutputStream os){//有参构造 传入输出流
this();
bw = new BufferedWriter(new OutputStreamWriter(os));
}
public Response println(String msg){//一个直接添加正文的方法
len += (msg+CRLF).getBytes().length;
content.append(msg).append(CRLF);
return this;
}
public Response print(String msg){
len += msg.getBytes().length;
content.append(msg);
return this;
}
public void pushToBrowse(int code) throws IOException{//发送方法 并调用响应头信息 将响应头状态传入
if(headInfo==null){
code = 505;
}
headInfo(code);
bw.append(headInfo.toString());
bw.append(content.toString());
bw.flush();
}
private void headInfo(int code){
//拼接响应协议
//返回响应协议 响应状态栏 HTTP/1.1 200 OK
headInfo.append("HTTP/1.1").append(blank);
headInfo.append(code).append(blank);
switch(code){
case 200:
headInfo.append("OK").append(CRLF);
break;
case 404:
headInfo.append("NOT FOUND").append(CRLF);
break;
case 505:
headInfo.append("SERVER ERROR").append(CRLF);
break;
}
//返回响应头 :
/*
* Date:Mon,31Dec209904:25:57GMT
Server:shsxt Server/0.0.1;charset=GBK
Content-type:text/html
Content-lenqth: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:").append("text/html").append(CRLF);
headInfo.append("Content-length:").append(len).append(CRLF);
headInfo.append(CRLF);
headInfo.append(content.toString());//拼接正文
}
}
package com.pzy.server.core;
public interface Servlet {
void service(Request request,Response response);
}
package com.pzy.server.core;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
public class WebApp {
private static WebContext context;
static{
try{
//SAX解析
//1.获取sax工厂
SAXParserFactory factory = SAXParserFactory.newInstance();
//2.获取sax解析器
SAXParser parser = factory.newSAXParser();
//3.加载文档document 注册处理器
WebHanlder hanlder = new WebHanlder();
//Thread.currentThread().getContextClassLoader()从当前线程的类加载器中获取
parser.parse(Thread.currentThread().getContextClassLoader().getResourceAsStream(“web.xml”)
,hanlder);
context = new WebContext(hanlder.getEntitys(),hanlder.getMappings());
}catch(Exception e){
System.out.println(“解析配置文件错误”);
}
}
/**
* 通过url获取对应文件的Servlet
* @param url
* @return
*/
public static Servlet getServlet(String url){
String name = context.getClz("/"+url);
if(name==null){
return null;
}
Class clz;
try {
clz = Class.forName(name);
Servlet ser = (Servlet) clz.newInstance();
return ser;
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}
package com.pzy.server.core;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class WebContext {
private List entitys = null;
private List mappings = null;
//key – > servlet-name value – > servlet-class
private Map<String,String> entityMap = new HashMap<String,String>();
//key -- > pattren value -- > servlet-name
private Map<String,String> mappingMap = new HashMap<String,String>();
public WebContext(List<Entity> entitys, List<Mapping> mappings) {
this.entitys = entitys;
this.mappings = mappings;
for(Entity entity : entitys){
entityMap.put(entity.getName(), entity.getClz());
}
for(Mapping mapping : mappings){
Set<String> pattrens = mapping.getPatterns();
for(String pattren : pattrens){
mappingMap.put(pattren, mapping.getName());
}
}
}
public String getClz(String pattern){
String name = entityMap.get(mappingMap.get(pattern));
return name==null?null:name;
}
}
package com.pzy.server.core;
import java.util.ArrayList;
import java.util.List;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
public class WebHanlder extends DefaultHandler{
private List entitys;
private List mappings;
private Entity entity;
private Mapping mapping;
private String tag;
private boolean isMapping = false;
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
// TODO Auto-generated method stub
String msg = new String(ch,start,length).trim();
if(isMapping){
if(msg.length()>0){
if(tag.equals("servlet-name")){
mapping.setName(msg);
}else if(tag.equals("url-pattern")){
mapping.addPattern(msg);
}
}
}else{
if(msg.length()>0){
if(tag.equals("servlet-name")){
entity.setName(msg);
}else if(tag.equals("servlet-class")){
entity.setClz(msg);
}
}
}
}
@Override
public void startDocument() throws SAXException {
// TODO Auto-generated method stub
entitys = new ArrayList<Entity>();
mappings = new ArrayList<Mapping>();
}
@Override
public void endDocument() throws SAXException {
// TODO Auto-generated method stub
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
// TODO Auto-generated method stub
if(qName!=null){
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 endElement(String uri, String localName, String qName) throws SAXException {
// TODO Auto-generated method stub
if(qName.equals("servlet")){
entitys.add(entity);
}else if(qName.equals("servlet-mapping")){
mappings.add(mapping);
}
tag = null;
}
public List<Entity> getEntitys() {
return entitys;
}
public List<Mapping> getMappings() {
return mappings;
}
}
package com.pzy.server.core;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
/**
- 加入对404以及505的分发处理
- @author H
*/
public class Dispatcher implements Runnable{
private Socket socket;
private Request request;
private Response response;
public Dispatcher(Socket socket){
this.socket = socket;
try {
request = new Request(socket);
response = new Response(socket);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
this.releals();
}
}
@Override
public void run() {
//准备正文
try {
if(request.getUrl()==null||request.getUrl().equals("")){
response.print(this.getHtml("index.html"));
response.pushToBrowse(200);
return ;
}
Servlet servlet = WebApp.getServlet(request.getUrl());//使用servlet将request和response参数传入进去 使得可以获得不同的页面效果 增加了耦合性
if(servlet!=null){
servlet.service(request, response);
response.pushToBrowse(200);
}else{
String data = this.getHtml("Error.html");
response.print(data);
response.pushToBrowse(404);
}
} catch (IOException e) {
try {
response.print("服务器正在疯狂修理中...");
response.pushToBrowse(505);
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
this.releals();
}
private String getHtml(String path){
InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(path);
byte base[] = new byte[1024*2];
int len;
try {
len = is.read(base);
String data = new String(base,0,len);
is.close();
return data;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
private void releals(){
try {
socket.close();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
package com.pzy.server.core;
/**
- login com.sxt.server.basic.servlet.LoginServlet
- @author H
*/
public class Entity {
private String name;
private String clz;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getClz() {
return clz;
}
public void setClz(String clz) {
this.clz = clz;
}
public Entity(String name, String clz) {
this.name = name;
this.clz = clz;
}
public Entity() {
}
}
package com.pzy.server.core;
import java.util.HashSet;
import java.util.Set;
/**
- reg /reg
- @author H
*/
public class Mapping {
private String name;
private Set patterns;
public Mapping(String name, Set patterns) {
this.name = name;
this.patterns = patterns;
}
public Mapping() {
patterns = new HashSet();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set getPatterns() {
return patterns;
}
public void setPatterns(Set patterns) {
this.patterns = patterns;
}
public void addPattern(String pattern){
this.patterns.add(pattern);
}
}
package com.pzy.user;
import com.pzy.server.core.Request;
import com.pzy.server.core.Response;
import com.pzy.server.core.Servlet;
public class LoginServlet implements Servlet {
@Override
public void service(Request request,Response response) {
response.print("<html>");
response.print("<head>");
response.print("<meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">");
response.print("<title>");
response.print("LoginServlet");
response.print("</title>");
response.print("</head>");
response.print("<body>");
//内容
response.print("我LoginServlet响应啦~~");
response.print("欢迎你:"+request.getValue("uname")+"~~");
response.print("</body>");
response.print("</html>");
}
}
package com.pzy.user;
import com.pzy.server.core.Request;
import com.pzy.server.core.Response;
import com.pzy.server.core.Servlet;
public class OtherServlet implements Servlet {
@Override
public void service(Request request, Response response) {
response.println("其他页面");
response.println("heihei");
response.println("other");
}
}
package com.pzy.user;
import com.pzy.server.core.Request;
import com.pzy.server.core.Response;
import com.pzy.server.core.Servlet;
public class RegisterServlet implements Servlet {
@Override
public void service(Request request,Response response) {
response.print("<html>");
response.print("<head>");
response.print("<meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">");
response.print("<title>");
response.print("第一个网页响应协议");
response.print("</title>");
response.print("</head>");
response.print("<body>");
//内容
response.print("我响应啦~~");
response.print("</body>");
response.print("</html>");
}
}
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>404页面</title>
</head>
<body>
<h1>你懂的</h1>
</body>
</html>
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>首页</title>
</head>
<body>
<h1>欢迎使用我的首页哦</h1>
</body>
</html>
<html>
<head>
<title>登录页面</title>
</head>
<body>
<h1>你好啊</h1>
<form method="get" action="http://localhost:8870/login">
用户名:<input type="text" name="uname" id="uname"/>
密码:<input type="password" name="pwd" id="pwd"/>
<input type="submit" value="登录"/>
</form>
</body>
</html>
<?xml version="1.0" encoding="UTF-8"?> login com.pzy.user.LoginServlet login /login /g
<servlet-mapping>
<servlet-name>reg</servlet-name>
<url-pattern>/reg</url-pattern>
<url-pattern>/hahah</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>reg</servlet-name>
<servlet-class>com.pzy.user.RegisterServlet
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>999</servlet-name>
<url-pattern>/o</url-pattern>
<url-pattern>/other</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>999</servlet-name>
<servlet-class>com.pzy.user.OtherServlet
</servlet-class>
</servlet>