一、知识体系
1、面向对象
特点:概括为封装性、继承性和多态性
2、IO流
用于获取请求协议和发送响应信息
3、多线程
使得服务器能够并发访问
4、xml解析
解析web.xml配置文件
5、反射
概念:把java类中的各种结构(方法、属性、构造器、类名)映射成一个个的java对象
目标:通过用户访问的url解析成类名并获取相应的servlet对象,以此来返回不同的页面
6、http协议
HTTP是一个应用层协议,由请求和响应构成,是一个标准的客户端服务器模型。HTTP是一个无状态的协议。
底层是基于socket的短链接实现。
目标:
1、使用ServerSocket建立与浏览器的连接, 获取请求协议
2、返回响应协议 (封装响应信息)
二、获取请求协议及封装
1、获取请求协议
//1、创建ServerSocket
ServerSocket serverSocket = new ServerSocket(8888);
//2、建立连接获取Socket
Socket client = serverSocket.accept();
System.out.println("一个客户端建立了连接...");
//3、通过输入流获取请求协议
InputStream is = client.getInputStream();
paramMap = new HashMap<>();
byte[] datas = new byte[1024*1024];
int len = 0;
try {
len = is.read(datas);
String requestInfo = new String(datas,0,len);
} catch (IOException e) {
e.printStackTrace();
}
请求协议格式:
GET请求:
POST请求:
2、封装request
目的:
1、获取用户访问的url和请求参数
根据获取的请求协议,并通过分割字符串获取请求方法、请求url和请求参数(需要注意的是get请求参数是通过?直接跟在url后面,post请求在最后一行,与请求体之间有一个空行)
下面通过封装request并对请求协议进行处理
package com.swust.java.practice.http;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.util.*;
/**
* 封装请求协议
* 1、获取 method uri 请求参数
* 2、封装请求参数为map
*/
public class Request {
// 请求协议信息
private String requestInfo;
// 请求方式
private String method;
// 请求url
private String url;
// 请求参数
private String queryParam;
// 处理请求参数
private Map<String, List<String>> paramMap;
private final String CRLF = "\r\n";
public Request() {}
public Request(Socket client) throws IOException {
this(client.getInputStream());
}
public Request(InputStream is) {
paramMap = new HashMap<>();
byte[] data = new byte[1024*1024];
int len = 0;
try {
len = is.read(data);
requestInfo = new String(data,0,len - 1);
} catch (IOException e) {
e.printStackTrace();
}
// 解析字符串
parseRequestInfo();
}
private void parseRequestInfo(){
System.out.println("--解析数据--");
//System.out.println(requestInfo);
// 1、获取请求方式
method = requestInfo.substring(0,requestInfo.indexOf("/")).trim().toLowerCase();
// 2、获取请求路径 路径中可能包含参数
url = requestInfo.substring(requestInfo.indexOf("/")+1,requestInfo.indexOf("HTTP/")).trim();
if(url.contains("?")){
String[] array = url.split("\\?");
queryParam = array[1].trim();
url = array[0].trim();
}
// 3、获取请求参数 空行之后 请求体
if(method.equals("post")){
String param = requestInfo.substring(requestInfo.lastIndexOf(CRLF)).trim();
if(null == queryParam){
queryParam = param;
}else{
queryParam += "&" + param;
}
}
queryParam = null == queryParam ? "" : queryParam;// 避免get请求无参数时为空
System.out.println("请求方法:"+method);
System.out.println("请求路径:"+url);
System.out.println("请求参数:"+queryParam);
paramConvertToMap(queryParam);
}
// 将请求参数由字符串转成map name=bob&pwd=123&age=23&age=18
private void paramConvertToMap(String queryParam){
//1、分割字符串
String[] array = queryParam.split("&");
for(String kv : array){
String[] s = kv.trim().split("=");
s = Arrays.copyOf(s,2);// 防止出现无参数值的情况
String key = s[0];
String value = s[1] == null ? null : decode(s[1],"UTF-8");
// 2、存储
if(!paramMap.containsKey(key)){// 当map中不包含key
paramMap.put(key,new ArrayList<>());
}
paramMap.get(key).add(value);
}
}
// 处理中文乱码问题
private String decode(String value,String enc){
try {
return java.net.URLDecoder.decode(value,enc);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
// 获取指定key的所有参数值
public String[] getParamValues(String key){
List<String> values = paramMap.get(key);
if(null == values || values.size() < 1){
return null;
}
return values.toArray(new String[0]);
}
// 获取指定key的一个参数值
public String getParam(String key){
String[] values = getParamValues(key);
return values == null ? null : values[0];
}
public String getMethod() {
return method;
}
public String getUrl() {
return url;
}
public String getQueryParam() {
return queryParam;
}
}
三、返回响应协议及封装
1、返回响应协议
final String BLANK = "";
final String CRLF = "\r\n";
//1、准备内容
StringBuilder content = new StringBuilder();
content.append("<html>");
content.append("<html>");
content.append("<head>");
content.append("<title>");
content.append("response success (*^▽^*)");
content.append("</title>");
content.append("</head>");
content.append("<body>");
content.append("<h2>登录页面->服务器响应成功!!!</h2>");
content.append("</body>");
content.append("</html>");
//2、获取字节数
int size = content.toString().getBytes().length;
//3、拼接响应协议
StringBuilder headInfo = new StringBuilder();
headInfo.append("HTTP/1.1").append(BLANK)
.append(statusCode).append(BLANK)
.append("OK").append(CRLF);// 响应行
headInfo.append("Date:").append(new Date()).append(CRLF)
.append("Server:").append("hqf Server/0.0.1").append(CRLF)
.append("Content-type:text/html;charset=UTF-8").append(CRLF)
.append("Content-length:").append(len).append(CRLF)
.append(CRLF);// 响应头
//4、使用输出流输出到browser
try {
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
bw.append(headInfo).append(content).flush();
} catch (IOException e) {
e.printStackTrace();
}
2、封装响应协议
目的:
1、能够动态添加内容
2、根据状态码拼接头信息
package com.swust.java.practice.http;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.Date;
/**
* 封装响应信息
* 1、动态添加内容
* 2、根据状态码拼接头信息
*/
public class Response {
BufferedWriter bw;
// 协议头信息
private StringBuilder headInfo;
// 正文
private StringBuilder content;
// 正文字节数
private int len;
private final String BLANK = " ";
private final String CRLF = "\r\n";
private Response() {
headInfo = new StringBuilder();
content = 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));
}
// 构建头信息 (状态码)
private void createHeadInfo(int statusCode){
// 1、响应行
headInfo.append("HTTP/1.1").append(BLANK)
.append(statusCode).append(BLANK);
switch (statusCode){
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、响应头
headInfo.append("Date:").append(new Date()).append(CRLF)
.append("Server:").append("hqf Server/0.0.1").append(CRLF)
.append("Content-type:text/html;charset=UTF-8").append(CRLF)
.append("Content-length:").append(len).append(CRLF)
.append(CRLF);
}
// 动态添加Response内容
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 statusCode){
if(null == headInfo){
statusCode = 500;
}
createHeadInfo(statusCode);
try {
bw.append(headInfo).append(content).flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
四、引入Servlet
通过引入servlet来处理不同的业务代码
通过接口中的service方法来构建不同的返回内容,response.pushToBrowser来返回不同的状态码
示例:
接口Servlet.java
package com.swust.java.practice.servlet;
import com.swust.java.practice.http.Request;
import com.swust.java.practice.http.Response;
/**
* 服务器脚本接口
*/
public interface Servlet {
void service(Request request, Response response);
void doGet(Request request, Response response);
void doPost(Request request, Response response);
}
LoginServlet.java
package com.swust.java.practice.servlet;
import com.swust.java.practice.http.Request;
import com.swust.java.practice.http.Response;
public class LoginServlet implements Servlet {
@Override
public void service(Request request, Response response) {
response.print("<html>");
response.print("<html>");
response.print("<head>");
response.print("<title>");
response.print("response success (*^▽^*)");
response.print("</title>");
response.print("</head>");
response.print("<body>");
response.print("<h2>登录页面->服务器响应成功!!!</h2>"+request.getParam("name"));
response.print("</body>");
response.print("</html>");
}
@Override
public void doGet(Request request, Response response) {
}
@Override
public void doPost(Request request, Response response) {
}
}
五、解析web.xml配置文件
目标:
1、通过servlet-mapping中的url-pattern获取servlet中的servlet-class(之后使用该类名并通过反射获取对应的servlet对象)
web.xml文件
//一、通过sax解析文件获取servlet对象和servlet-mapping对象并分别保存到一个列表中
//SAX解析
//1、获取解析工厂
SAXParserFactory factory = SAXParserFactory.newInstance();
//2、从解析工厂获取解析器
SAXParser parser = factory.newSAXParser();
//3、加载文档Document注册处理器
WebHandler handler = new WebHandler();
//4、编写处理器
//5、解析 使用当前类加载器获取xml资源
parser.parse(Thread.currentThread().
getContextClassLoader().
getResourceAsStream("com/swust/java/practice/web.xml"),
handler);
// 获取数据
List<Entity> entities = handler.getEntities();
List<Mapping> mappings = handler.getMappings();
//二、上下文处理
WebContext webContext = new WebContext(entities,mappings);
String name = webContext.getClz(url); // 通过url获取对应类名
//三、根据反射获取Servlet对象
try {
// 获取类对象
Class clz = Class.forName(name);
// 实例化对象
com.swust.java.practice.servlet.Servlet servlet = (com.swust.java.practice.servlet.Servlet) clz.getConstructor().newInstance();
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
六、封装分发器
目的:
通过多线程实现并发访问
package com.swust.java.practice.dispatcher;
import com.swust.java.practice.http.Request;
import com.swust.java.practice.http.Response;
import com.swust.java.practice.servlet.Servlet;
import com.swust.java.practice.web.WebApp;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
/**
* 分发器
*/
public class Dispatcher implements Runnable{
private Socket client;
private Request request;
private Response response;
public Dispatcher(Socket client) {
this.client = client;
try {
/**
* 获取请求协议
*/
this.request = new Request(client);
/**
* 返回响应协议
*/
this.response = new Response(client);
} catch (IOException e) {
e.printStackTrace();
this.release();
}
}
@Override
public void run() {
try{
if(null == request.getUrl() || request.getUrl().equals("")){// 首页
loadResource("index");
response.pushToBrowser(200);
return;
}
Servlet servlet = WebApp.getServletFromUrl(request.getUrl());
if(null != servlet){// 找到了对应的servlet
servlet.service(request,response);
// 返回状态码
response.pushToBrowser(200);
}else{// 404
loadResource("404");
response.pushToBrowser(404);
}
}catch (Exception e){
loadResource("500");
response.pushToBrowser(500);
}
release();// 短链接 提高性能
}
// 根据页面名称加载资源
private void loadResource(String pageName){
try {
// 通过类加载器查找资源
InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("com/swust/java/practice/views/"+pageName+".html");
byte[] bytes = new byte[1024 * 1024];
is.read(bytes);
response.print(new String(bytes));
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
// 释放资源
private void release(){
try {
this.client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
7、完结
Server.java
开启服务、关闭服务和接收请求协议
package com.swust.java.practice;
import com.swust.java.practice.dispatcher.Dispatcher;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 目标:
* 1、使用ServerSocket建立与浏览器的连接, 获取请求协议
* 2、返回响应协议 (封装响应信息)
*/
public class Server {
private ServerSocket serverSocket;
private boolean isRunning;
// 开启服务
public void start(){
try {
serverSocket = new ServerSocket(8888);
isRunning = true;
} catch (IOException e) {
e.printStackTrace();
System.out.println("服务器启动失败...");
}
}
// 接受连接并进行处理
public void receive(){
while (isRunning){
try {
// 多线程处理
Socket client = serverSocket.accept();
System.out.println("一个客户端建立了连接...");
new Thread(new Dispatcher(client)).start();
} catch (IOException e) {
e.printStackTrace();
System.out.println("客户端连接异常...");
this.stop();
}
}
}
// 停止服务
public void stop(){
isRunning = false;
try {
serverSocket.close();
System.out.println("服务器已停止");
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Server server = new Server();
server.start();
server.receive();
}
}
项目结构: