GET请求:
GET /index?name=123&psw=fdskf HTTP/1.1
Host: localhost:8888
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
POST请求:
POST /index HTTP/1.1
Host: localhost:8888
Connection: keep-alive
Content-Length: 34
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: null
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
username=fdsfgsdfg&pwd=fdsfadsfasd
对两种不同的方式的请求信息解析
2.Request封装
先解析下请求:
第一行: 请求方式 请求资源 HTTP协议版本
后面几行是一些协议,客户端支持的数据格式
如果是POST请求,请求参数会放在最后一行,和上面一行有空行间隔,如果是GET方式,请求参数会放在第一行
2.1 得到浏览器的请求信息
通过构造方法将socket的输入流传入,读取解析
2.2 得到请求方式与请求资源
解析请求信息,字符串截取第一行,然后截取方法,根据方法判断,如果是GET,那么请求资源与请求参数在第一行,如果是POST,第一行是请求资源,最后一行是请求参数,然后解析请求资源
public Request(InputStream inputStream) { this(); this.inputStream = inputStream;
//从输入流中取出请求信息
try {
byte[] data = new byte[20480];
int len = inputStream.read(data);
requestInfo=new String(data, 0, len); //解析请求信息
parseRequestInfo();
} catch (Exception e) { return;
}
} /**
* 解析请求信息
* @param requestInfo2
*/
private void parseRequestInfo() {
if(requestInfo==null || requestInfo.trim().equals("")) { return;
} String paramentString="";//保存请求参数
//得到请求第一行数据
String firstLine = requestInfo.substring(0, requestInfo.indexOf(CRLF));
//第一个/的位置
int index = firstLine.indexOf("/"); this.method = firstLine.substring(0,index).trim();
String urlString = firstLine.substring(index,firstLine.indexOf("HTTP/")).trim(); //判断请求方式
if (method.equalsIgnoreCase("post")) {
url = urlString; //最后一行就是参数
paramentString = requestInfo.substring(requestInfo.lastIndexOf(CRLF)).trim();
}else if (method.equalsIgnoreCase("get")) { if (!urlString.contains("?")) { this.url = urlString;
}else { //分割url
String[] urlArray = urlString.split("\\?"); this.url = urlArray[0];
paramentString = urlArray[1];
}
}
if (paramentString!=null&&!paramentString.trim().equals("")) { //解析请求参数
parseParament(paramentString);
}
}
/**
* 解决中文乱码
* @param value
* @param code
* @return
*/
private String decode(String value,String code) { try { return URLDecoder.decode(value, code);
} catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block
e.printStackTrace();
} return null;
}
/**
* 解析请求参数,放在数组里面
* name=12&age=13&fav=1&fav=2
*/
private void parseParament(String paramentString) {
String[] paramentsArray = paramentString.split("&");
for(String string:paramentsArray) { //某个键值对的数组
String[] paramentArray = string.split("="); //如果该键没有值,设值为null
if (paramentArray.length==1) {
paramentArray = Arrays.copyOf(paramentArray, 2);
paramentArray[1]=null;
}
String key = paramentArray[0]; String value = paramentArray[1]==null?null:decode(paramentArray[1].trim(), "utf8");
//分拣法
if (!paramentMap.containsKey(key)) {
paramentMap.put(key,new ArrayList<String>());
}
//设值
ArrayList<String> values = paramentMap.get(key);
values.add(value);
}
}
2.3 根据name得到请求参数的值
上一步解析完数据之后,会把请求参数放在Map中,key为请求参数name,value为请求参数值,根据key得到值
/**
* 根据key得到多个值
*/
public String[] getParamenters(String name) {
ArrayList<String> values =null; if ((values=paramentMap.get(name))==null) { return null;
}else { return values.toArray(new String[0]);
}
}
/**
* 根据key得到值
*/
public String getParamenter(String name) {
if ((paramentMap.get(name))==null) { return null;
}else { return getParamenters(name)[0];
}
}
2.4 解决中文乱码
前台提交数据时候,中文有时候会乱码,
/**
* 解决中文乱码
* @param value
* @param code
* @return
*/
private String decode(String value,String code) { try { return URLDecoder.decode(value, code);
} catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block
e.printStackTrace();
} return null;
}
3. 封装response
当得到浏览器请求之后,需要给浏览器响应
响应格式:
响应:
3.1 得到服务器的输出流
构造方法传入socket的输出流
3.2 构造响应头
常见响应码:200 404 500
根据状态码,构建不同的响应头,
3.3 构建方法,外界传入响应正文
暴露一个方法,用于外界传入响应值,与状态码
3.4 构建响应正文
根据不同响应码构建响应正文,如404,返回一个NOT FOUNF页面,
500返回一个SERVER ERROR 页面,如果是200,就返回正常界面,
3.5 构建推送数据到客户端的方法
将响应推送到客户端
4. 封装servlet
将响应与请求封装在一个servlet类中,主要是实现业务逻辑,不做其他事情 , 在server端直接new 一个servlet类,调用业务方法
public class Servlet {
public void service(Request request,Response response){
response.print("<html>\r\n" +
"<head>\r\n" +
" <META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=UTF-8\">\r\n" +
"</head>\r\n" +
"<body>\r\n" +
"欢迎你\r\n" + request.getParamenter("username")+ "</form>\r\n" +
"</body>\r\n" +
"</html>");
}
}
5. 处理不同请求的server
想让server可以处理不同请求,/login 是做登录请求 /reg 是做注册请求
需要多线程,当有请求过来之后,创建一个线程处理相关的请求与响应,每个请求的线程互不影响,
5.1 创建一个转发器
每有一个客户端连接,就会创建一个线程,处理改客户端的请求与响应
public class Dispatcher implements Runnable{ private Request request; private Response response; private Socket client;
private int code=200;
public Dispatcher(Socket client) { try {
client = client;
request = new Request(client.getInputStream());
response = new Response(client.getOutputStream());
} catch (IOException e) {
code=500; return;
}
}
@Override
public void run() {
Servlet servlet = new Servlet();
servlet.service(request, response);
response.pushToclient(code);
CloseUtils.closeSocket(client);
}
}
5.2 创建上下文对象,存放servlet与对应的mapping
使用工厂模式,得到不同url得到不同的servlet,
public class ServletContext {
/*
* 有LoginServlet 设值别名 login
* 访问login /login /log
*/
//存放servlet的别名
private Map<String, Servlet> servletMap; //存放url对应的别名
private Map<String, String> mappingMap;
public ServletContext() {
servletMap = new HashMap<>();
mappingMap = new HashMap<>();
}
public class WebApp {
private static ServletContext context;
static {
context = new ServletContext(); //存放servlet 和其对应的别名
Map<String, Servlet> servletMap = context.getServletMap();
servletMap.put("login", new LoginServlet());
servletMap.put("register", new RegisterServlet());
Map<String, String> mappingMap = context.getMappingMap();
mappingMap.put("/login", "login");
mappingMap.put("/", "login");
mappingMap.put("/register", "register");
mappingMap.put("/reg", "register");
}
public static Servlet getServlet(String url) {
if (url==null || url.trim().equals("")) { return null;
}else { return context.getServletMap().get(context.getMappingMap().get(url));
}
}
}
6.反射获取servlet对象
根据请求的url, 在mappingmap中找到servlet的别名,根据servlet的别名在servletmap中得到servlet对象,map存对象过于耗费内存,并且,每次添加一个servlet,都要更改这个文件,所以讲servlet的配置,卸载xml文件中,读取xml文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<servlet>
<servlet-name>login</servlet-name>别名 <servlet-class>jk.zmn.server.demo4.LoginServlet</servlet-class>类的全路径 </servlet>
<servlet-mapping>配置映射 <servlet-name>login</servlet-name>别名 <url-pattern>/login</url-pattern> 访问路径 <url-pattern>/</url-pattern> 访问路径 </servlet-mapping>
<servlet>
<servlet-name>reg</servlet-name>
<servlet-class>jk.zmn.server.demo4.RegisterServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>reg</servlet-name>
<url-pattern>/reg</url-pattern>
</servlet-mapping>
</web-app>
6.1 解析xml文件
首先要先解析xml配置文件,得到servlet及其映射,
6.2 根据解析到的数据,动态添加到map中
//获取解析工厂
try {
SAXParserFactory factory =SAXParserFactory.newInstance(); //获取解析器
SAXParser sax =factory.newSAXParser(); //指定xml+处理器
WebHandler web = new WebHandler();
sax.parse(Thread.currentThread().getContextClassLoader()
.getResourceAsStream("jk/zmn/server/demo4/web.xml")
,web);
//得到所有的servlet 和别名
List<ServletEntity> entityList = web.getEntityList();
List<MappingEntity> mappingList = web.getMappingList();
context = new ServletContext(); //存放servlet 和其对应的别名
Map<String, String> servletMap = context.getServletMap();
for(ServletEntity servletEntity: entityList) {
servletMap.put(servletEntity.getName(),servletEntity.getClz());
} //存放urlpatten和servlet别名
Map<String, String> mappingMap = context.getMappingMap(); for(MappingEntity mappingEntity:mappingList) {
List<String> urlPattern = mappingEntity.getUrlPattern(); for(String url:urlPattern) {
mappingMap.put(url, mappingEntity.getName());
}
}
不用每次都直接修改这个文件,直接在配置文件中配置就行
##########################################################
最后:我已将文件抽好,

image.png
servlet包,是用户自定义包,新建的servlet必须要继承servlet类,
web.xml必须在src目录下,配置servlet也需按照格式配置,
运行core java application, 浏览器访问你的项目,就可以正常运行了,
效果:

image.png

image.png

image.png
作者:z七夜
链接:https://www.jianshu.com/p/2c608b1baccf