1.前言
版本:v1.0.0 MyTomcat
功能:访问静态资源文件(html,css,js)
工具:Maven+Idea
此篇博客Mytomcat实现的功能只能访问静态资源(图片处理点问题),我们把项目部署到Mytomcat的webapps目录下,开启MyTomcat服务后,就可以进行访问了!
csdn下载:点击下载
github下载:点击下载
2.需要准备的知识
- Socket编程
- HTTP协议
- 反射
- xml解析
- 多线程
3.设计步骤
1.封装response
A:构建报文头
B:构建响应的HTML正文内容
C:将报文头和HTML正文内容发送给客户端(浏览器)
2.封装request
A:接受浏览器发送的请求
B:解析浏览器发送来的请求
3.建Servlet类来转码处理请求和响应的业务
A.实现service方法,传入request,response
4.多线程实现多的客户端发请求
A.把与客户端浏览器的通信封装到一个线程当中
5.启动MyTomcat服务
A.Bootstrap类中的start()方法
4.代码实现
4.1 封装request
package cxx.catalina;
import java.io.*;
import java.net.URLDecoder;
import java.util.*;
/**
* 代表一个HTTP请求,它的构造方法传入一个InputStream输入流
* @Author: cxx
* @Date: 2018/6/22 9:33
*/
public class Request {
private static final String ENTER = "\r\n";
//接收请求
private BufferedReader br ;
//储存接受信息
private String requestHeader;
//通过解析头信息得到请求方法
private String method ;
//通过解析头信息得到请求url
private String action ;
//通过解析头信息得到传过来的请求参数 ,可能存在一Key多Value的情况所以用list
private Map<String, List<String>> parameter;
//得到浏览器发过来的头信息
public Request() {
requestHeader = "";
method = "";
action = "";
parameter = new HashMap<String, List<String>>();
}
public Request(InputStream is){
this();
br=new BufferedReader(new InputStreamReader(is));
//接收到头部信息
try {
String temp;
while(!(temp=br.readLine()).equals("")){
requestHeader += (temp+ENTER);
}
System.out.println(requestHeader);
} catch (Exception e) {
e.printStackTrace();
}
//解析头部信息
parseRequestHeader();
}
/**
* 解析头信息
*/
public void parseRequestHeader(){
//声明一个字符串,来存放请求参数
String parameterString = "";
//读取都头信息的第一行
String firstLine = requestHeader.substring(0, requestHeader.indexOf(ENTER));
//开始分离第一行
//splitPoint分割点1
int splitPointOne = firstLine.indexOf("/");
method = firstLine.substring(0, splitPointOne).trim();
//splitPoint分割点2
int splitPointTwo = firstLine.indexOf("HTTP/");
String actionTemp = firstLine.substring(splitPointOne,splitPointTwo).trim();
if(method.equalsIgnoreCase("post")){
//此处代码为得到post请求的参数字符串,哈哈哈哈,读者自己想想该怎么写哦~~
this.action = actionTemp;
}else if(method.equalsIgnoreCase("get")){
if(actionTemp.contains("?")){
parameterString = actionTemp.substring((actionTemp.indexOf("?")+1)).trim();
this.action = actionTemp.substring(0, actionTemp.indexOf("?"));
}else{
this.action = actionTemp;
}
//将参数封装到Map中哦
try {
parseParameterString(parameterString);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 解析参数字符串,将参数封装到Map中
* @param parameterString
*/
private void parseParameterString(String parameterString) throws Exception {
if("".equals(parameterString)){
return;
}else{
String[] parameterKeyValues = parameterString.split("&");
for (int i = 0; i < parameterKeyValues.length; i++) {
String[] KeyValues = parameterKeyValues[i].split("=");
//可能会出现有key没有value的情况
if(KeyValues.length == 1){
KeyValues = Arrays.copyOf(KeyValues, 2);
KeyValues[1] = null;
}
String key = KeyValues[0].trim();
String values = null == KeyValues[1] ? null : decode(KeyValues[1].trim(),"UTF-8");
//将key和values封装到Map中
if(!parameter.containsKey(key)){//如果不存在key,就创建一个
parameter.put(key, new ArrayList<String>());
}
List<String> value = parameter.get(key);
value.add(values);
}
}
}
/**
* 反解码:使用指定的编码机制对 application/x-www-form-urlencoded 字符串解码。
* @param string
* @param encoding
* @return
*/
public String decode(String string,String encoding){
try {
return URLDecoder.decode(string, encoding);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
/**
* 根据名字得到多个值
* @param name
* @return
*/
public String[] getParamterValues(String name){
List<String> values = parameter.get(name);
if(values == null){
return null;
}else{
return values.toArray(new String[0]);
}
}
/**
* 根据名字返回单个值
* @param name
* @return
*/
public String getParamter(String name){
String[] value = getParamterValues(name);
if(value == null){
return null;
}else{
return value[0];
}
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
}
4.2 封装response
package cxx.catalina;
import java.io.*;
import java.util.Date;
/**
* Http响应
* @Author: cxx
* @Date: 2018/6/22 9:45
*/
public class Response {
private static final int BUFFER_SIZE=1024;
private static final String SPACE=" ";
private static final String ENTER = "\r\n";
//头信息
private StringBuilder headerInfo;
//正文信息
private StringBuilder textContent;
//正文信息长度
private int contentLength;
//构建输出流
private BufferedWriter bw;
//获取request请求
private Request request;
public Response(){
headerInfo=new StringBuilder();
textContent=new StringBuilder();
contentLength=0;
}
public Response(OutputStream os,Request request){
this();
this.request=request;
this.bw=new BufferedWriter(new OutputStreamWriter(os));
}
/**
* 创建头部信息 html报文
* @param code
*/
private void createHeader(int code){
String type = request.getAction();
headerInfo.append("HTTP/1.1").append(SPACE).append(code).append(SPACE);
switch (code) {
case 200:
headerInfo.append("OK").append(ENTER);
break;
case 404:
headerInfo.append("NOT FOUND").append(ENTER);
break;
case 500:
headerInfo.append("SERVER ERROR").append(ENTER);
break;
default:
break;
}
headerInfo.append("Server:myServer").append(SPACE).append("0.0.1v").append(ENTER);
headerInfo.append("Date:Sat,"+SPACE).append(new Date()).append(ENTER);
headerInfo.append("Content-Length:").append(contentLength).append(ENTER);
if(type.endsWith("html")){
headerInfo.append("Content-Type:text/html;charset=UTF-8").append(ENTER);
}
if (type.endsWith("ico")){
headerInfo.append("Content-Type:application/octet-stream;charset=UTF-8").append(ENTER);
}
if (type.endsWith("png")||type.endsWith("jpg")){
System.out.println("图片资源");
headerInfo.append("Content-Type:image/jpeg;charset=UTF-8").append(ENTER);
}
headerInfo.append(ENTER);
}
/**
* 响应给浏览器解析的内容(html正文)
* @param content
* @return
*/
public Response htmlContent(String content){
textContent.append(content).append(ENTER);
contentLength+=(content+ENTER).toString().getBytes().length;
return this;
}
/**
* 发送给浏览器端
* @param code
*/
public void pushToClient(int code){
createHeader(code);
try {
bw.append(headerInfo.toString());
System.out.println(headerInfo.toString());
bw.append(textContent.toString());
bw.flush();
}catch (Exception e){
e.printStackTrace();
}finally {
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
4.3 建Servlet类来转码处理请求和响应的业务
package cxx.tomcat.server.http;
import cxx.catalina.Request;
import cxx.catalina.Response;
import cxx.tomcat.server.http.HttpServer;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
/**
* 专门处理请求和响应
* @Author: cxx
* @Date: 2018/6/22 21:39
*/
public class Servlet {
private static final int BUFFER_SIZE=1024;
public void service(Request request, Response response){
System.out.println("service方法");
byte[] b = new byte[BUFFER_SIZE];
FileInputStream fis = null;
try {
StringBuilder contextText = new StringBuilder();
File file = new File(HttpServer.WEB_ROOT + request.getAction());
fis = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fis);
int ch = 0;
while ((ch = bis.read(b)) != -1) {
contextText.append(new String(b, 0, ch));
}
String username = request.getParamter("user");
//response.htmlContent("<html><head></head><body>This is my page<br><br>");
//response.htmlContent("欢迎:"+username+" 来到我的地盘</body></html>");
response.htmlContent(contextText.toString());
}catch (Exception e) {
e.printStackTrace();
}
}
}
4.4 多线程实现多的客户端发请求
package cxx.tomcat.server.http;
import cxx.catalina.Request;
import cxx.catalina.Response;
import java.net.Socket;
/**
* 多线程实现 多的客户端发请求
* @Author: cxx
* @Date: 2018/6/22 23:02
*/
public class HttpServerThread implements Runnable {
private Socket client;
private Request request;
private Response response;
private int code = 200;
public HttpServerThread(Socket client){
this.client=client;
try{
request=new Request(client.getInputStream());
response=new Response(client.getOutputStream(),request);
}catch (Exception e){
code=500;
return;
}
}
@Override
public void run() {
Servlet servlet = new Servlet();
servlet.service(request,response);
response.pushToClient(code);
try {
client.close();
}catch (Exception e){
System.out.println(e.getMessage());
}
}
}
4.5启动MyTomcat服务
package cxx.tomcat.server;
import cxx.tomcat.server.http.HttpServerThread;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 启动MyTomcat
* @Author: cxx
* @Date: 2018/6/22 23:11
*/
public class Bootstrap {
private boolean isShutDown = false;
/**
* 启动服务器
*/
public void start(){
int port=8888;
System.out.println("Mytomcat服务开启.....http://127.0.0.1:"+port+"/login.html");
start(port);
}
/**
* 关闭服务器
*/
private void stop() {
isShutDown = true;
System.out.println("Mytomcat服务关闭.....");
}
/**
* 指定服务器端口
* @param port
*/
public void start(int port){
try {
ServerSocket serverSocket = new ServerSocket(port);
//2、接收来自浏览器的请求
this.recevie(serverSocket);
} catch (Exception e) {
stop();
}
}
/**
* 接受客户端信息
* @param serverSocket
*/
private void recevie(ServerSocket serverSocket){
try {
while(!isShutDown){
Socket client = serverSocket.accept();
new Thread(new HttpServerThread(client)).start();
}
} catch (Exception e) {
//如果这里面有问题直接关闭服务器
isShutDown = true;
}
}
public static void main(String[] args) {
Bootstrap boot = new Bootstrap();
boot.start();
}
}