解决代码冗余,响应工作重用。
设计:
与请求相似,设计一个类HttpResponse,用它每个实例表示要给客户端回复的响应内容,然后在ClientHandler开始工作时将该对象实例化,并在处理请求的过程中将要响应的内容设置到该对象,并在响应客户端的工作中统一将该对象内容以标准的响应格式发送给客户端。
把处理响应的代码抽取出来,减少代码冗余。
package cn.tedu.vip.webserver.http;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
/**
* 响应客户端请求的对象
* 每一个请求对应一个响应
* 响应对象包含如下部分
* 状态行,响应头,响应正文
* @author Tedu
*
*/
public class HttpResponse {
//状态行相关信息
private int statusCode=200;//状态码
private String statusReason="OK";//状态结果
//响应头相关信息
//响应正文文件
private File entity;
//连接对象和输出流
private Socket socket;
private OutputStream out;
//构造方法,为socket和out赋值
public HttpResponse(Socket socket) {
try {
this.socket=socket;
this.out=socket.getOutputStream();
} catch (Exception e) {
e.printStackTrace();
}
}
/*
* 将当前响应对象的内容以标准的http协议格式响应给客户端
*
*/
public void flush() {
try {
//发送状态行
System.out.println("开始发送状态行");
sendStatusLine();
//发送响应头
System.out.println("开始发送消息头");
sendHerders();
//发送响应正文
System.out.println("开始发送消息正文");
sendContent();
} catch (Exception e) {
e.printStackTrace();
}
}
/*
* 发送状态行
*/
public void sendStatusLine() {
try {
String line = "HTTP/1.1"+" "+statusCode+" "+statusReason;
out.write(line.getBytes("ISO8859-1"));
out.write(13);
out.write(10);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/*
* 发送响应头
*/
public void sendHerders() {
try {
//遍历headers,发送每个响应头信息
for(Entry<String,String> e: headers.entrySet()) {
String key=e.getKey();
String value=e.getValue();
String line=key+": "+value;
System.out.println("响应头:"+line);
out.write(line.getBytes("ISO8859-1"));
out.write(13);
out.write(10);
}
//单独发送一个换行表示响应头发送完毕
out.write(13);
out.write(10);
} catch (Exception e) {
e.printStackTrace();
}
}
// 发送响应正文
public void sendContent() {
try (
FileInputStream fis=new FileInputStream(entity)
){
byte[] data=new byte[1024*10];
int len=-1;
while((len=fis.read(data))!=-1) {
out.write(data,0,len);
}
}catch(Exception e) {
e.printStackTrace();
}
}
public File getEntity() {
return entity;
}
public void setEntity(File entity) {
this.entity = entity;
}
public int getStatusCode() {
return statusCode;
}
public void setStatusCode(int statusCode) {
this.statusCode = statusCode;
}
public String getStatusReason() {
return statusReason;
}
public void setStatusReason(String statusReason) {
this.statusReason = statusReason;
}
//这个方法不是自动生成的要自己写
public void putHeader(String key,String value) {
this.headers.put(key, value);
}
}
package cn.tedu.vip.webserver.core;
import java.io.File;
import java.net.Socket;
import com.webserver.http.HttpRequest;
import com.webserver.http.HttpResponse;
/**
* 用于处理客户端交互的业务类
* @author Tedu
*
*/
public class ClientHandler implements Runnable{
private Socket socket;
public ClientHandler(Socket socket) {
this.socket=socket;
}
public void run() {
/*
* Http协议要求一问一答的交互方式
* 客户端发送请求,服务器响应结果
* ClientHandler处理客户端请求有三个步骤
*1:解析请求
*1.1解析请求行
*1.2解析消息头
*1.3解析消息正文
*2:处理请求
*3:发送响应
*/
try {
//1:实例化HttpRequest对象,解析请求
HttpRequest request=new HttpRequest(socket);
//2:处理请求
//2.1通过request获取请求的抽象路径
String path=request.getUri();
System.out.println("抽象路径:"+path);
//2.2通过该路径去webapps目录下寻找该资源
File file=new File("./webapps"+path);
//2.3判断该资源是否存在
if(file.exists()){
System.out.println("该资源已找到!");
response.setEntity(file);
}else{
System.out.println("该资源不存在!");
File notFoundFile=new File("./webapps/root/404.html");
response.setStatusCode(404);
response.setStatusReason("Not Found");
response.setEntity(notFoundFile);
}
//响应客户端
response.flush();
}catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
//处理完毕后与客户端断开连接
try{
socket.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
}
在indexhtml页面中显示一张图片。
一个客户端请求的页面中,如果包含了图片等其他信息,;那么就相当于又发送了一次请求来请求图片等资源。
要求服务器端能连续的处理请求,在WebServer类接收请求的代码上加上循环结构来实现。
package cn.tedu.vip.webserver.core;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* WebServer项目的主类
* 启动这个类,开启WebServer程序
*
* 这个程序功能是启动之后,可以使用浏览器来发送请求
* 我们编写代码对浏览器的请求作出响应的效果
* @author Tedu
*
*/
public class WebServer {
//声明服务器对象,浏览器访问服务器的地址和端口
private ServerSocket server;
public WebServer() {
try {
System.out.println("服务器端正在启动。。。");
server=new ServerSocket(8088);
threadPool=Executors.newFixedThreadPool(50);
System.out.println("服务器端启动完成!");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void start() {
try {
while(true) {
System.out.println("等待客户端连接");
Socket socket=server.accept();
System.out.println("一个客户端连接了");
//启动一个线程处理该客户端交互
ClientHandler handler=new ClientHandler(socket);
Thread t=new Thread(handler);
t.start();
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) {
WebServer server=new WebServer();
server.start();
}
}
为了更好的返回响应,将本类flush方法中的代码也分为三步:
package cn.tedu.vip.webserver.http;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
/**
* 响应客户端请求的对象
* 每一个请求对应一个响应
* 响应对象包含如下部分
* 状态行,响应头,响应正文
* @author Tedu
*
*/
public class HttpResponse {
//状态行相关信息
private int statusCode=200;//状态码
private String statusReason="OK";//状态结果
//响应头相关信息
//key:响应头名字 value:响应头的值
private Map<String,String>headers=new HashMap<>();
//响应正文相关信息
//响应正文文件
private File entity;
//连接对象和输出流
private Socket socket;
private OutputStream out;
//构造方法,为socket和out赋值
public HttpResponse(Socket socket) {
try {
this.socket=socket;
this.out=socket.getOutputStream();
} catch (Exception e) {
e.printStackTrace();
}
}
/*
* 将当前响应对象的内容以标准的http协议格式响应给客户端
*
*/
public void flush() {
try {
//发送状态行
//System.out.println("开始发送状态行");
sendStatusLine();
//发送响应头
//System.out.println("开始发送消息头");
sendHerders();
//发送响应正文
//System.out.println("开始发送消息正文");
sendContent();
} catch (Exception e) {
e.printStackTrace();
}
}
/*
* 发送状态行
*/
public void sendStatusLine() {
try {
String line = "HTTP/1.1"+" "+statusCode+" "+statusReason;
out.write(line.getBytes("ISO8859-1"));
out.write(13);
out.write(10);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/*
* 发送响应头
*/
public void sendHerders() {
try {
//遍历headers,发送每个响应头信息
for(Entry<String,String> e: headers.entrySet()) {
String key=e.getKey();
String value=e.getValue();
String line=key+": "+value;
System.out.println("响应头:"+line);
out.write(line.getBytes("ISO8859-1"));
out.write(13);
out.write(10);
}
//单独发送一个换行表示响应头发送完毕
out.write(13);
out.write(10);
} catch (Exception e) {
e.printStackTrace();
}
}
// 发送响应正文
public void sendContent() {
try (
FileInputStream fis=new FileInputStream(entity)
){
System.out.println("HttpResponse:开始发送响应正文...");
//发送响应正文
byte[] data=new byte[1024*10];
int len=-1;
while((len=fis.read(data))!=-1) {
out.write(data,0,len);
}
System.out.println("HttpResponse:发送响应正文完毕!");
}catch(Exception e) {
e.printStackTrace();
}
}
public File getEntity() {
return entity;
}
public void setEntity(File entity) {
this.entity = entity;
}
public int getStatusCode() {
return statusCode;
}
public void setStatusCode(int statusCode) {
this.statusCode = statusCode;
}
public String getStatusReason() {
return statusReason;
}
public void setStatusReason(String statusReason) {
this.statusReason = statusReason;
}
//这个方法不是自动生成的要自己写
public void putHeader(String key,String value) {
this.headers.put(key, value);
}
}
本类使用一个Map保存了一些常见的文件后缀名和mime类型的对应关系,以备请求出现时使用
package cn.tedu.vip.webserver.core;
import java.io.File;
import java.net.Socket;
import com.webserver.http.HttpRequest;
import com.webserver.http.HttpResponse;
/**
* 用于处理客户端交互的业务类
* @author Tedu
*
*/
public class ClientHandler implements Runnable{
private Socket socket;
public ClientHandler(Socket socket) {
this.socket=socket;
}
public void run() {
/*
* Http协议要求一问一答的交互方式
* 客户端发送请求,服务器响应结果
* ClientHandler处理客户端请求有三个步骤
*1:解析请求
*1.1解析请求行
*1.2解析消息头
*1.3解析消息正文
*2:处理请求
*3:发送响应
*/
try {
//1:实例化HttpRequest对象,解析请求
HttpRequest request=new HttpRequest(socket);
//实例化响应对象
HttpResponse response=new HttpResponse(socket);
//2:处理请求
//2.1通过request获取请求的抽象路径
String path=request.getUri();
System.out.println("抽象路径:"+path);
//2.2通过该路径去webapps目录下寻找该资源
File file=new File("./webapps"+path);
//2.3判断该资源是否存在
if(file.exists()){
System.out.println("该资源已找到!");
response.setEntity(file);
Map<String,String> map=new HashMap<>();
map.put("html","text/html");
map.put("png","image/png");
map.put("jpg","image/jpeg");
map.put("gif","image/gif");
map.put("css","text/css");
//获取请求资源的文件名 logo.png
String fileName=file.getName();
int index=fileName.lastIndexOf(".");//最后一个点出现的位置
String ext=fileName.substring(index+1);//获得后缀名:png
String mime=map.get(ext);
response.putHeader("Content-Type", mime);
response.putHeader("Content-Length", file.length()+"");
}else{
System.out.println("该资源不存在!");
File notFoundFile=new File("./webapps/root/404.html");
response.setStatusCode(404);
response.setStatusReason("Not Found");
response.setEntity(notFoundFile);
response.putHeader("Content-Type", mime);
response.putHeader("Content-Length", file.length()+"");
}
//响应客户端
response.flush();
}catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
//处理完毕后与客户端断开连接
try{
socket.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
}
XML指可扩展标记语言(eXtensible Markup Language)
<?xml version="1.0" encoding="UTF-8"?>
<note>
<to>tom</to>
<from>jreey</from>
<title>好久不见</title>
<content>特别想念你</content>
</note>
XML被设计用来传输和储存数据。
实体引用 字符 说明
< < 小于
> > 大于
& & 与字符(和字符)
&apos ‘ 单引号
" “ 双引号
<![CDATA [文本内容]]>
上面格式中[文本内容]中,无论出现了什么特殊字符或符号,全部被认为是普通文本
将XML文件中的内容读取到java中
SAX:速度快,但是不能修改元素内容
DOM:速度慢,但是可以修改元素容易
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.tedu.vip</groupId>
<artifactId>Unit009</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<!-- 这里按照固定格式下载需要的jar包 这个固定格式称之为坐标 编写坐标后,maven会自动到远程中央仓库下载指定的jar包 http://maven.aliyun.com/mvn/view搜索下载jar
dom4j=dom for java
log4j=log for java
-->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
</dependencies>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<list>
<emp id="e1">
<name>王五</name>
<gender>男</gender>
<age>30</age>
<salary>5000</salary>
</emp>
<emp id="e2">
<name>赵四</name>
<gender>男</gender>
<age>35</age>
<salary>4500</salary>
</emp>
<emp id="e3">
<name>刘三</name>
<gender>男</gender>
<age>31</age>
<salary>6000</salary>
</emp>
</list>
package cn.tedu.vip.xml;
public class Emp {
private String id;
private String name;
private String gender;
private int age;
private int salary;
public Emp() {
}
public Emp(String id, String name, String gender, int age, int salary) {
super();
this.id = id;
this.name = name;
this.gender = gender;
this.age = age;
this.salary = salary;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getSalary() {
return salary;
}
public void setSalary(int salary) {
this.salary = salary;
}
}
package cn.tedu.vip.xml;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
/**
* 使用dom4j解析XML文档
* @author Tedu
*
*/
public class ParseXmlDemo {
public static void main(String[] args) {
/*
* 解析XML的步骤分为4步
* 1:创建一个SAXReader
* 2:使用SAXReader读取XML获得一个Document对象
* 注意:这个步骤是比较耗时的
* 将XML文件全部解析完才能获得Document对象
* 3:根据Document对象获得根元素
* 4:通过根元素获得所有XML的元素
* 也称遍历XML元素
*/
try {
//1
SAXReader reader=new SAXReader();
//2
Document doc=reader.read(new File("./emp.xml"));
//3
/*
* 相关API
* doc提供了获得根元素的方法
* Element getRootElement();
*
* 每个Element又包含一些方法
* 一个ELement对象对应一组标签
*
* String getName()返回标签的名字
* String getText()返回标签中包含的文本
* Element element(String name)获得当前标签中指定名字的子标签
* List elements()返回当前标签中的所有子标签
* List elements(String name)返回当前标签中指定名字的所有子标签
*/
Element root=doc.getRootElement();
//4
//准备一个集合接收所有员工
List<Emp> emps=new ArrayList<>();
//获取当前根元素的所有子元素
List<Element> els=root.elements("emp");
//遍历所有元素els
for(Element el:els) {
//获取员工id
//Attribute att=el.attribute("id");
//String id=att.getValue();
//System.out.println(id);
String id=el.attributeValue("id");
System.out.println(id);
//获得员工姓名
Element nameEl=el.element("name");
String empName=nameEl.getText();
//System.out.println(empName);
//获得员工性别
String empGender=el.elementText("gender");
System.out.println(empGender);
String stringAge=el.elementText("age");
int empAge=Integer.parseInt(stringAge);
//System.out.println(empAge);
int empSalary=Integer.parseInt(el.elementText("salary"));
Emp emp=new Emp(id, empName, empGender, empAge, empSalary);
emps.add(emp);
}
//遍历xml的for循环结束了
System.out.println("解析完毕");
for(Emp e:emps) {
System.out.println("id:"+e.getId()+",姓名:"+e.getName()+",性别:"+e.getGender()+",年龄:"+e.getAge()+",工资:"+e.getSalary());
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
问题:
在ClientHandler处理请求的环节,无论是找到资源还是未找到资源设置404页面,都要在设置正文文件同时设置,设置对应的响应头Content-Type与Content-Length,因此将这两个头的工作放在setEntity方法中,这样每次设置正文的时候就自动设置这两个头了
解决:
package cn.tedu.vip.webserver.http;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
/**
* HTTP规定的固定内容全在这个类中
* @author Tedu
*
*/
public class HttpContext {
/*
* 保存所有后缀名对应mineType类型的map
* key:后缀
* value:mime
*/
private static Map<String,String> mimeMapping=new HashMap<>();
static {
//编写一个方法专门处理mimeMapping的静态资源
initMimeMapping();
}
private static void initMimeMapping() {
/*
* 赋值web.xml文件到项目中:conf/web.xml
* 使用dom4j,在pom.xml导入坐标
* 获得根元素后,将根元素中所有名为:mime-mapping的子元素获取
* 子元素中名为extension作为map的key
* 子元素中名为mime-type作为map中的value
*/
try {
SAXReader reader=new SAXReader();
Document doc=reader.read(new File("conf/Web.xml"));
Element root=doc.getRootElement();
List<Element> list=root.elements("mime-mapping");
for (Element el : list) {
String key=el.elementText("extension");
String value=el.elementText("mime-type");
mimeMapping.put(key, value);
}
//输出静态map的长度
System.out.println(mimeMapping.size());
} catch (DocumentException e) {
e.printStackTrace();
}
}
/*
* 根据给定的后缀名返回这个后缀对应的mimeType
*/
public static String getMimeType(String ext) {
return mimeMapping.get(ext);
}
public static void main(String[] args) {
//String type=mimeMapping.get("exe");
String type=HttpContext.getMimeType("exe");
System.out.println(type);
}
}
减少ClientHandler类响应操作的代码冗余,将setEntity方法重构一下,并在其他使用我们创建的HttpContext类来获取文件后缀名对应的mime类型,放入Content-Type中
package cn.tedu.vip.webserver.http;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
/**
* 响应客户端请求的对象
* 每一个请求对应一个响应
* 响应对象包含如下部分
* 状态行,响应头,响应正文
* @author Tedu
*
*/
public class HttpResponse {
//状态行相关信息
private int statusCode=200;//状态码
private String statusReason="OK";//状态结果
//响应头相关信息
//key:响应头名字 value:响应头的值
private Map<String,String>headers=new HashMap<>();
//响应正文相关信息
//响应正文文件
private File entity;
//连接对象和输出流
private Socket socket;
private OutputStream out;
//构造方法,为socket和out赋值
public HttpResponse(Socket socket) {
try {
this.socket=socket;
this.out=socket.getOutputStream();
} catch (Exception e) {
e.printStackTrace();
}
}
/*
* 将当前响应对象的内容以标准的http协议格式响应给客户端
*
*/
public void flush() {
try {
//发送状态行
System.out.println("开始发送状态行");
sendStatusLine();
//发送响应头
System.out.println("开始发送消息头");
sendHerders();
//发送响应正文
System.out.println("开始发送消息正文");
sendContent();
} catch (Exception e) {
e.printStackTrace();
}
}
/*
* 发送状态行
*/
private void sendStatusLine() {
try {
System.out.println("HttpResponse:开始发送状态行....");
String line = "HTTP/1.1"+" "+statusCode+" "+statusReason;
out.write(line.getBytes("ISO8859-1"));
out.write(13);
out.write(10);
System.out.println("HttpResponse:发送状态行完毕!");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/*
* 发送响应头
*/
private void sendHerders() {
try {
//遍历headers,发送每个响应头信息
for(Entry<String,String> e: headers.entrySet()) {
String key=e.getKey();
String value=e.getValue();
String line=key+": "+value;
System.out.println("响应头:"+line);
out.write(line.getBytes("ISO8859-1"));
out.write(13);
out.write(10);
}
//单独发送一个换行表示响应头发送完毕
out.write(13);
out.write(10);
} catch (Exception e) {
e.printStackTrace();
}
}
// 发送响应正文
private void sendContent() {
try (FileInputStream fis=new FileInputStream(entity)){
byte[] data=new byte[1024*10];
int len=-1;
while((len=fis.read(data))!=-1) {
out.write(data,0,len);
}
}catch(Exception e) {
e.printStackTrace();
}
}
public File getEntity() {
return entity;
}
/*
*设置响应正文文件的同时会根据该文件自动
*添加响应头:Content-Type与Content-Length
*/
public void setEntity(File entity) {
/*
* 在Clinethandler调用这个方法时,直接将后续的操作放到这个方法中
* 达到减少Clienthandler类中代码冗余的目的
*/
this.entity = entity;
String fname=entity.getName();
int index=fname.lastIndexOf(".");//获得后缀
String ext=fname.substring(index+1);
String mime=HttpContext.getMimeType(ext);//type
//设置文件头信息
putHeader("Content-Type",mime);//type
putHeader("Content-Length", entity.length()+"");
}
public int getStatusCode() {
return statusCode;
}
public void setStatusCode(int statusCode) {
this.statusCode = statusCode;
}
public String getStatusReason() {
return statusReason;
}
public void setStatusReason(String statusReason) {
this.statusReason = statusReason;
}
//这个方法不是自动生成的要自己写
public void putHeader(String key,String value) {
this.headers.put(key, value);
}
}
package cn.tedu.vip.webserver.core;
import java.io.File;
import java.net.Socket;
import com.webserver.http.HttpRequest;
import com.webserver.http.HttpResponse;
/**
* 用于处理客户端交互的业务类
* @author Tedu
*
*/
public class ClientHandler implements Runnable{
private Socket socket;
public ClientHandler(Socket socket) {
this.socket=socket;
}
public void run() {
/*
* Http协议要求一问一答的交互方式
* 客户端发送请求,服务器响应结果
* ClientHandler处理客户端请求有三个步骤
*1:解析请求
*1.1解析请求行
*1.2解析消息头
*1.3解析消息正文
*2:处理请求
*3:发送响应
*/
try {
//1:实例化HttpRequest对象,解析请求
HttpRequest request=new HttpRequest(socket);
//实例化响应对象
HttpResponse response=new HttpResponse(socket);
//2:处理请求
//2.1通过request获取请求的抽象路径
String path=request.getUri();
System.out.println("抽象路径:"+path);
//2.2通过该路径去webapps目录下寻找该资源
File file=new File("./webapps"+path);
//2.3判断该资源是否存在
if(file.exists()){
System.out.println("该资源已找到!");
response.setEntity(file);
}else{
System.out.println("该资源不存在!");
File notFoundFile=new File("./webapps/root/404.html");
response.setStatusCode(404);
response.setStatusReason("Not Found");
response.setEntity(notFoundFile);
}
//响应客户端
response.flush();
}catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
//处理完毕后与客户端断开连接
try{
socket.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
}
原文:https://www.cnblogs.com/xiongchenglong/p/14586468.html