持续学习&持续更新中…
守破离
【尚学堂】手写WebServer
Web请求都是使用Reauest和Response式的交流
预备—反射
什么是反射(Reflect)
反射是框架设计的灵魂
获取Class的三种方式
//三种方式
//1、对象.getClass()
Iphone iphone =new Iphone();
Class clz = iphone.getClass();
//2、类.class
clz = Iphone.class;
//3、Class.forName("包名.类名")
clz = Class.forName("com.sxt.server.basic.Iphone");
利用反射创建对象的两种方式
//创建对象
// 方式一(不推荐)
Iphone iphone1 =(Iphone)clz.newInstance();
// 方式二(推荐)
Iphone iphone2 =(Iphone)clz.getConstructor().newInstance();
预备—XML解析
简单解析
p.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<persons>
<person>
<name>至尊宝</name>
<age>9000</age>
</person>
<person>
<name>白晶晶</name>
<age>7000</age>
</person>
</persons>
解析:
//SAX解析
//1、获取解析工厂
SAXParserFactory factory = SAXParserFactory.newInstance();
//2、从解析工厂获取解析器
SAXParser parse = factory.newSAXParser();
//3、编写处理器
//4、加载文档 Document 注册处理器
PersonHandler handler = new PersonHandler();
//5、解析
parse.parse(Thread.currentThread().getContextClassLoader()
.getResourceAsStream("p.xml")
, handler);
final List<Person> persons = handler.getPersons();
for (Person person : persons) {
System.out.println(person);
}
PersonHandler:
class PersonHandler extends DefaultHandler {
private final List<Person> persons = new ArrayList<>();
private Person person;
private String currentTag;
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if (qName.equals("person")) {
person = new Person();
}
currentTag = qName;
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (qName.equals("person")) {
persons.add(person);
}
currentTag = null;
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
String content = new String(ch, start, length).trim();
if (currentTag != null) {
if ("name".equals(currentTag)) {
person.setName(content);
} else if ("age".equals(currentTag)) {
if (!"".equals(content)) {
person.setAge(Integer.valueOf(content));
}
}
}
}
public List<Person> getPersons() {
return persons;
}
}
解析web.xml
web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<servlet>
<servlet-name>login</servlet-name>
<servlet-class>programmer.lp.webserver.servlet.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>login</servlet-name>
<url-pattern>/login/*</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>register</servlet-name>
<servlet-class>programmer.lp.webserver.servlet.RegisterServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>register</servlet-name>
<url-pattern>/register/*</url-pattern>
</servlet-mapping>
</web-app>
Entity :
package programmer.lp.webserver.webconfig;
/*
<servlet>
<servlet-name>login</servlet-name>
<servlet-class>programmer.lp.basic.servlet.LoginServlet</servlet-class>
</servlet>
*/
public class Entity {
private String name;
private String clz;
public Entity() {
}
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;
}
}
Mapping :
package programmer.lp.webserver.webconfig;
/*
<servlet-mapping>
<servlet-name>login</servlet-name>
<url-pattern>/login</url-pattern>
<url-pattern>/g</url-pattern>
</servlet-mapping>
*/
import java.util.HashSet;
import java.util.Set;
public class Mapping {
private String name;
private Set<String> urlPatterns;
public Mapping() {
urlPatterns = new HashSet<>();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<String> getUrlPatterns() {
return urlPatterns;
}
public void setUrlPatterns(Set<String> urlPatterns) {
this.urlPatterns = urlPatterns;
}
public void addUrlPattern(String urlPattern) {
urlPatterns.add(urlPattern);
}
}
WebHandler :
package programmer.lp.webserver.webconfig;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import java.util.ArrayList;
import java.util.List;
public class WebHandler extends DefaultHandler {
private final List<Entity> entities = new ArrayList<>();
private final List<Mapping> mappings = new ArrayList<>();
private Entity entity;
private Mapping mapping;
private String currentTag;
private boolean isMapping;
public List<Entity> getEntities() {
return entities;
}
public List<Mapping> getMappings() {
return mappings;
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if (qName.equals("servlet")) {
entity = new Entity();
isMapping = false;
} else if (qName.equals("servlet-mapping")) {
mapping = new Mapping();
isMapping = true;
}
currentTag = qName;
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (qName.equals("servlet")) {
entities.add(entity);
} else if (qName.equals("servlet-mapping")) {
mappings.add(mapping);
}
currentTag = null;
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
String content = new String(ch, start, length).trim();
if (currentTag != null) {
if (isMapping) {
if ("servlet-name".equals(currentTag)) {
mapping.setName(content);
} else if ("url-pattern".equals(currentTag)) {
mapping.addUrlPattern(content);
}
} else {
if ("servlet-name".equals(currentTag)) {
entity.setName(content);
} else if ("servlet-class".equals(currentTag)) {
entity.setClz(content);
}
}
}
}
}
WebContext :
package programmer.lp.webserver.webconfig;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class WebContext {
private final Map<String, String> entityMap = new HashMap<>();
private final Map<String, String> mappingMap = new HashMap<>();
private final Map<String, String> mappingALlMap = new HashMap<>();
public WebContext(List<Entity> entities, List<Mapping> mappings) {
for (Entity entity : entities) {
entityMap.put(entity.getName(), entity.getClz());
}
for (Mapping mapping : mappings) {
final Set<String> urlPatterns = mapping.getUrlPatterns();
for (String urlPattern : urlPatterns) {
if (urlPattern.endsWith("*")) {
urlPattern = urlPattern.substring(0, urlPattern.lastIndexOf("/*"));
mappingALlMap.put(urlPattern, mapping.getName());
} else {
mappingMap.put(urlPattern, mapping.getName());
}
}
}
}
public String getServletClass(String urlPattern) {
final Set<Map.Entry<String, String>> entries = mappingALlMap.entrySet();
for (Map.Entry<String, String> entry : entries) {
final String key = entry.getKey();
if (urlPattern.startsWith(key)) {
return entityMap.get(mappingALlMap.get(key));
}
}
return entityMap.get(mappingMap.get(urlPattern));
}
}
结合反射与XML配置文件
package programmer.lp.webserver.webconfig;
import programmer.lp.webserver.servlet.Servlet;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
public class WebApp {
private static WebContext webContext;
static {
try {
//SAX解析
//1、获取解析工厂
SAXParserFactory factory = SAXParserFactory.newInstance();
//2、从解析工厂获取解析器
SAXParser parse = factory.newSAXParser();
//3、编写处理器
//4、加载文档 Document 注册处理器
WebHandler handler = new WebHandler();
//5、解析
parse.parse(Thread.currentThread()
.getContextClassLoader()
.getResourceAsStream("web.xml")
, handler);
webContext = new WebContext(handler.getEntities(), handler.getMappings());
} catch (Exception e) {
e.printStackTrace();
}
}
public static Servlet getServletFromUri(String uri) {
// final String uri = "/register";
final String servletClass = webContext.getServletClass(uri);
try {
return (Servlet) Class.forName(servletClass).getConstructor().newInstance();
} catch (Exception e) {
return null;
}
}
}
预备—HTTP协议
HTTP协议是应用层协议,底层基于TCP协议。
Request—GET
GET / HTTP/1.1
Host: localhost:8888
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3878.400 QQBrowser/10.8.4518.400
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.9
GET /?username=lp&password=1234 HTTP/1.1
Host: localhost:8888
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.9 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Request—POST
Request方法是POST时,也可以在浏览器地址栏中加上请求参数,会将这些参数与请求BODY中的参数一块解析。
POST /index.html HTTP/1.1
Host: localhost:8888
Connection: keep-alive
Content-Length: 15
Cache-Control: max-age=0
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.9 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
uname=lp&pwd=12
Response
响应格式:
HTTP/1.1 200 OK
Date: Tue, 16 Nov 2021 14:48:29 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 44543
响应正文
手写WebServer(简易)
以后有时间的话,再仔细研究研究。
封装Request
package programmer.lp.webserver.server;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.util.*;
public class Request {
private static final String CRLF = "\r\n";
private String requestInfo;
private String method; // 小写
private String uri; // 前面带/
private String params = ""; // 浏览器地址栏可以写参数
private Map<String, List<String>> paramsMap;
public Request(Socket client) throws IOException {
this(client.getInputStream());
}
public Request(InputStream is) {
paramsMap = new HashMap<>();
try {
byte[] buf = new byte[1024 * 1024];
int len = is.read(buf);
requestInfo = new String(buf, 0, len);
parseRequest();
} catch (Exception e) {
e.printStackTrace();
}
}
private void parseRequest() {
method = requestInfo.substring(0, requestInfo.indexOf(" /")).toLowerCase();
uri = requestInfo.substring(requestInfo.indexOf("/"), requestInfo.indexOf(" HTTP/"));
int indexOfParam = uri.indexOf("?");
if (indexOfParam >= 0) {
params += uri.substring(indexOfParam + 1);
uri = uri.substring(0, indexOfParam);
}
if ("post".equals(method)) {
String param = requestInfo.substring(requestInfo.lastIndexOf(CRLF)).trim();
if (params.equals("")) {
params += param;
} else {
params += "&";
params += param;
}
}
// System.out.println(method);
// System.out.println(uri);
// System.out.println(params);
parseParams();
}
private void parseParams() {
if (params.length() <= 0) return;
final String[] keyValues = params.split("&");
for (String keyValue : keyValues) {
String[] kvArr = keyValue.split("=");
kvArr = Arrays.copyOf(kvArr, 2);
String key = kvArr[0];
String value = kvArr[1];
if (!paramsMap.containsKey(key)) {
paramsMap.put(key, new ArrayList<>());
}
value = value == null ? "" : value;
if (method.equals("get")) {
value = decodeGetParam(value, "UTF-8");
}
paramsMap.get(key).add(value);
}
}
private String decodeGetParam(String value, String code) {
try {
return java.net.URLDecoder.decode(value, code);
} catch (UnsupportedEncodingException e) {
return value;
}
}
public String[] getParameters(String name) {
if (!paramsMap.containsKey(name)) return null;
return paramsMap.get(name).toArray(new String[]{
});
}
public String getParameter(String name) {
try {
return getParameters(name)[0];
} catch (Exception e) {
return "";
}
}
public String getMethod() {
return method;
}
public String getUri() {
return uri;
}
}
封装Response
package programmer.lp.webserver.server;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Date;
public class Response {
private static final String CRLF = "\r\n";
private static final String BLANK = " ";
private BufferedWriter writer;
private final StringBuilder responseContent = new StringBuilder();
private final StringBuilder responseHeaders = new StringBuilder();
public Response(Socket client) throws IOException {
this(client.getOutputStream());
}
public Response(OutputStream outputStream) {
try {
writer = new BufferedWriter(new OutputStreamWriter(outputStream));
} catch (Exception e) {
e.printStackTrace();
}
}
public Response print(String content) {
responseContent.append(content);
return this;
}
public Response println(String content) {
responseContent.append(content).append(CRLF);
return this;
}
public void createHeader(int code) {
//1、响应行: HTTP/1.1 200 OK
responseHeaders.append("HTTP/1.1").append(BLANK);
responseHeaders.append(code).append(BLANK);
String status = "OK";
switch (code) {
case 404:
status = "NOT FOUND";
break;
case 500:
status = "SERVER ERROR";
break;
}
responseHeaders.append(status).append(CRLF);
//2、响应头(最后一行存在空行):
/*
Date: Mon,31Dec209904:25:57GMT
Content-Type: text/html
Content-Length: 39725426
*/
responseHeaders.append("Date: ").append(new Date()).append(CRLF);
responseHeaders.append("Content-Type: text/html").append(CRLF);
responseHeaders.append("Content-Length: ")
.append(responseContent.toString().getBytes(StandardCharsets.UTF_8).length)
.append(CRLF);
//3、空行
responseHeaders.append(CRLF);
}
public void pushToBrowser(int code) {
// 响应正文
try {
createHeader(code);
writer.append(responseHeaders.toString());
writer.append(responseContent.toString());
writer.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
}
封装Dispatcher
package programmer.lp.webserver.server;
import programmer.lp.webserver.servlet.Servlet;
import programmer.lp.webserver.webconfig.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 {
request = new Request(client);
response = new Response(client);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void run() {
try {
final String requestUri = request.getUri();
// if (requestUri.equals("") || requestUri.equals("/")) {
// goIndex();
// release();
// return;
// }
final Servlet servlet = WebApp.getServletFromUri(requestUri);
if (null != servlet) {
servlet.service(request, response);
response.pushToBrowser(200);
} else {
goError(404);
}
} catch (Exception e) {
e.printStackTrace();
goError(500);
}
release();
}
private void goError(int code) {
// 读取error.html页面
String info = readFromResource("error.html");
info = info.replace("#error#", code + "");
response.print(info);
response.pushToBrowser(code);
}
// private void goIndex() {
// // 读取index.html页面
// String info = readFromResource("index.html");
// response.print(info);
// response.pushToBrowser(200);
// }
private String readFromResource(String fileName) {
StringBuilder info = new StringBuilder();
try (final InputStream resourceAsStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName);) {
int len;
byte[] buf = new byte[1024];
while ((len = resourceAsStream.read(buf)) != -1) {
info.append(new String(buf, 0, len));
}
} catch (Exception e) {
e.printStackTrace();
}
return info.toString();
}
private void release() {
try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
封装Server
package programmer.lp.webserver.server;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
private ServerSocket serverSocket;
private boolean isRunning;
//启动服务
public void start() {
try {
serverSocket = new ServerSocket(8888);
isRunning = true;
receive();
} catch (IOException e) {
e.printStackTrace();
stop();
}
}
//接受连接处理
public void receive() {
while (isRunning) {
try {
Socket client = serverSocket.accept();
new Thread(new Dispatcher(client)).start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
//停止服务
public void stop() {
isRunning = false;
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Server server = new Server();
server.start();
}
}
参考
尚学堂: JAVA课程.
本文完,感谢您的关注支持!