测试类
public class TestHttpServer {
public static void main(String[] args) throws IOException, ClassNotFoundException {
MyHttpServer server = new MiniHttpServer(80);
server.startup();//启动服务
server.shutdown();//关闭服务
}
}
接口
public interface MyHttpServer {
void startup() throws IOException, ClassNotFoundException;
Request createRequest(ServletContext servletContext) throws IOException;
String service(ServletContext servletContext) throws IOException, ClassNotFoundException;
Response createResponse(ServletContext servletContext) throws IOException;
void shutdown();
}
接口实现类
/**
* 服务器
*/
public class MiniHttpServer implements MyHttpServer {
//定义变量记录请求数量,线程安全的计数器
private AtomicLong counter = new AtomicLong(0);
private ServerSocket serverSocket;
private Integer port;
/** thread pool for connection handlers */
private static final ExecutorService connectionThreadPool =
Executors.newFixedThreadPool(3);
public MiniHttpServer(int port) {
this.port = port;
}
public void startup() throws IOException, ClassNotFoundException {
//创建服务端socket通信对象
System.out.println("The server is started on port 80 with IP 172.171.3.37");
serverSocket = new ServerSocket(port);//构造参数传端口号。
while (true) {
Socket socket = serverSocket.accept();//程序阻塞等待客户端连接
//获取客户端地址
InetAddress clientAddr = socket.getInetAddress();
String clientHost = (clientAddr != null
? clientAddr.getHostAddress()
: "0.0.0.0");
System.out.println("The Request [" + counter.incrementAndGet() + "] client ip: " + clientHost);
connectionThreadPool.execute(//该方法会自动执行传入线程池里线程的run方法
new ConnectionHandler(socket, clientHost));
}
}
public void shutdown() {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
ConnetionHandler类
public class ConnectionHandler implements Runnable {
private Socket socket;
private String remoteHost;//客户端地址
public ConnectionHandler(Socket socket, String remoteHost) {
this.socket = socket;
this.remoteHost = remoteHost;
}
@Override
public void run() {
//创建一个web服务器也叫做web容器的上下文环境
System.out.println("The server starts to init the context");
ServletContext servletContext = new ServletContext();
//封装request对象的目的是为了后边处理逻辑可以只使用该对象即可,方便携带请求过来的数据。
//ServletContext
try {
//把socket套接字设置到上下文环境中
servletContext.setSocket(socket);
//该方法调用会在内部使用socket中的输入流读取客户端发过来的请求数据并且封装到Request对象里
System.out.println("The server init a request entity Request");
servletContext.setRequest(createRequest(servletContext));
//根据uri找到对应的java文件,也就是servlet文件,然后获取该java文件的执行内容,最后封装成数据放到上下文环境中
System.out.println("Server fetch user request data");
servletContext.setData(service(servletContext));
//使用上下文环境中的数据封装需要返回给用户的数据,这些数据大部分都是在service方法内部产生的。
System.out.println("The server init a response entity Response");
servletContext.setResponse(createResponse(servletContext));
System.out.println("Server context init successfully..........");
//最后返回给客户端它请求的具体数据。
response(servletContext);
socket.close();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
private void response(ServletContext servletContext) throws IOException {
Socket socket = servletContext.getSocket();
Request request = servletContext.getRequest();
Response response = servletContext.getResponse();
OutputStream outputStream = socket.getOutputStream();
PrintWriter out = new PrintWriter(outputStream);
String resp = response.buildResponse(request, response);
out.print(resp);
out.flush();
//关闭连接
//socket.close();
}
public Request createRequest(ServletContext servletContext) throws IOException {
InputStream inputStream = servletContext.getSocket().getInputStream();//获取输入流读取浏览器的请求数据
InputStreamReader isr = new InputStreamReader(inputStream);//转换流,把字节流转成字符流
//把转换后的字符流使用缓冲流包裹起来。因为我们要使用缓冲流的readline方法
BufferedReader br = new BufferedReader(isr);
//创建一个对象用于封请求的数据。此时创建出来的request对象引用里的各个变量参数都是空,需要我们读取请求过来的
//数据并把这些数据解析后存入该request对象里。存完后该对象才会有值。
Request request = new Request();
//下面开始读取流中的数据,并且把数据放到request对象内。
int count = 1;
//因为http请求的头部信息是key: value形式的字符串,所以我们可以使用键值对集合map来存储
HashMap<String, String> header = new HashMap<>();
String b = null;
while (!(b=br.readLine()).equals("")) {
System.out.println(b);
//为什么需要设置这个count,因为请求的第一行的格式不是键值对形式。
//GET /login HTTP/1.1
if (count == 1) {
String[] requests = b.split(" ");
//初始化对象里的属性
request.setMethod(requests[0]);
if (requests[1].equals("/"))
request.setUri("/index");
else {
request.setUri(requests[1]);
}
request.setVersion(requests[2]);
} else {
String[] split = b.split(":");
if (split != null && split.length ==2) {
//因为浏览器请求的header头部是按照key:value的形式存放,所以我们可以使用集合的Hasmap存储这部分内容
header.put(split[0], split[1].trim());
}
}
count++;
}
//把请求内容按照空格切割,切割后数组里的第二个元素就是路径名
request.setHeaders(header);
//流使用完成后需要手动关闭。
/*br.close();
isr.close();
inputStream.close();*/
//返回构建出来的request,此时浏览器请求过来的数据已经全部被封装进入request这个对象里了,以后使用请求数据
//只需要使用该request对象即可。
return request;
}
public String service(ServletContext servletContext) throws IOException, ClassNotFoundException {
//获取java文件的class路径
URL url = MyHttpServer.class.getResource("./");
String classFilePath = url.toString().split(":")[1];
String[] split = classFilePath.split("/");
classFilePath = split[split.length-1];
classFilePath = classFilePath + ".servlet";
//获取某个路径下的所有文件,使用File类
File file = new File(url.toString().split(":")[1] + "servlet/");// /Users/fcp/food/java/java2202/out/production/javase/socket/servlet/
String webpage = null;
//首先判断该file是否存在
if(file.exists()) {
File[] files = file.listFiles();
for (File f : files) {
if (f.getName().endsWith("Servlet.class")) {//只使用servlet文件
//通过字符串处理拼接类的全限定名
String classFileName = classFilePath;
classFileName = classFileName + "." + f.getName();
classFileName = classFileName.substring(0, classFileName.length()-6);
//通过反射获取该文件的Class对象。Class.forName("socket.servlet.xxxx");
Class<?> clazz = Class.forName(classFileName);
//查看该对象上的注解是否是WebServlet
if (clazz.isAnnotationPresent(WebServlet.class)) {
WebServlet annotation = clazz.getAnnotation(WebServlet.class);
//获取注解的value
String value = annotation.value();
//如果用户没有指定uri,那么就是访问网站的首页
if (value.equals("/")) value ="/index";
//判断该value是否和前端传入的地址相同
if (value.equals(servletContext.getRequest().getUri())) {
//通过反射创建该servlet对象,然后调用该对象的service方法
/* Object instance = clazz.getDeclaredConstructor().newInstance();
Method method = clazz.getDeclaredMethod("service");
method.invoke(instance);*/
//让服务器返回一个登录的html页面 login.html
//使用IO流读取html文件内容
FileInputStream fis = new FileInputStream("/Users/fcp/food/java/java2202/javase/src/html/" + value +".html");
byte[] bytes1 = new byte[1024*8];
fis.read(bytes1);
webpage = new String(bytes1);
}
}
}
}
}
return webpage;
/*createResponse(request, socket, s);*/
}
public Response createResponse(ServletContext servletContext) throws IOException {
Request request = servletContext.getRequest();
String data = (String) servletContext.getData();
//创建一个响应实体
Response response = new Response();
response.setCode(HttpConfig.HTTP_CODE);
response.setStatus(HttpConfig.HTTP_STATUS);
response.setVersion(request.getVersion());
//构建响应头部数据
Map<String,String> header = new HashMap<>();
header.put(HttpConfig.CONTENT_TYPE_KEY, HttpConfig.CONTENT_TYPE_VALUE);
response.setHeaders(header);
//存入响应数据
response.setMessage(data);
return response;
}
}
WebServlet注解类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface WebServlet {
String value() default "";
}
html页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
<style>
input {
width: 200px;
height: 30px;
border-radius: 10px;
}
.container {
width: 800px;
margin-left: auto;
margin-right: auto;
margin-top: auto;
margin-bottom: auto;
}
</style>
</head>
<body style="background-color: cornflowerblue">
<div class="container">
<label>用户名:</label>
<input type="text" name="username" placeholder="username"><br><br>
<label>密码:</label>
<input type="password" name="username" placeholder="password"><br><br>
<label>确认密码:</label>
<input type="password" name="username" placeholder="......."><br><br>
<label>邮箱:</label>
<input type="email" name="username" placeholder="eamil"><br><br>
<label>电话:</label>
<input type="phone" name="username" placeholder="phone">
</div>
</body>
</html>
//首页html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>主页</title>
<style>
.container {
display: flex;
width: 800px;
margin-left: auto;
margin-right: auto;
}
.img{
width: 300px;
height: 500px;
}
</style>
</head>
<body>
<div class="container">
<h1 style="margin-left: auto; margin-right: auto;">欢迎来到英雄联盟</h1>
<img class="img" src="./img/lol01.jpeg">
</div>
</body>
</html>
Request类
public class Request {
/**
* 请求方法 GET/POST/PUT/DELETE/OPTION...
*/
private String method;
/**
* 请求的uri
*/
private String uri;
/**
* http版本
*/
private String version;
/**
* 请求头
*/
private Map<String, String> headers;
/**
* 请求参数相关
*/
private String message;
}
Response类
public class Response {
private String version;
private int code;
private String status;
private Map<String, String> headers;
private String message;
public static String buildResponse(Request request, Response response) {
Response httpResponse = new Response();
httpResponse.setCode(response.getCode());
httpResponse.setStatus(response.getStatus());
httpResponse.setVersion(request.getVersion());
Map<String, String> headers = response.getHeaders();
Optional<String> optional = Optional.ofNullable(response.getMessage());
headers.put("Content-Length", String.valueOf(optional.orElseGet(() -> "").length()));
httpResponse.setHeaders(headers);
httpResponse.setMessage(response.getMessage());
StringBuilder builder = new StringBuilder();
buildResponseLine(httpResponse, builder);
buildResponseHeaders(httpResponse, builder);
buildResponseMessage(httpResponse, builder);
return builder.toString();
}
private static void buildResponseLine(Response response, StringBuilder stringBuilder) {
stringBuilder.append(response.getVersion()).append(" ").append(response.getCode()).append(" ")
.append(response.getStatus()).append("\n");
}
private static void buildResponseHeaders(Response response, StringBuilder stringBuilder) {
for (Map.Entry<String, String> entry : response.getHeaders().entrySet()) {
stringBuilder.append(entry.getKey()).append(":").append(entry.getValue()).append("\n");
}
stringBuilder.append("\n");
}
private static void buildResponseMessage(Response response, StringBuilder stringBuilder) {
stringBuilder.append(response.getMessage());
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public Map<String, String> getHeaders() {
return headers;
}
public void setHeaders(Map<String, String> headers) {
this.headers = headers;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
ServletContext类
public class ServletContext<T> {
private T data;
private Socket socket;
private Request request;
private Response response;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public Socket getSocket() {
return socket;
}
public void setSocket(Socket socket) {
this.socket = socket;
}
public Request getRequest() {
return request;
}
public void setRequest(Request request) {
this.request = request;
}
public Response getResponse() {
return response;
}
public void setResponse(Response response) {
this.response = response;
}
}
配置文件类HttpConfig
public class HttpConfig {
public static final Integer HTTP_CODE = 200;
public static final String HTTP_STATUS = "ok";
public static final String CONTENT_TYPE_KEY = "Content-Type";
public static final String CONTENT_TYPE_VALUE = "text/html;charset=UTF-8";
}
Servlet类如下
//浏览器输入地址 http://localhost 回车就可以访问
@WebServlet("/")//访问网站登录页
public class IndexServlet {
public void service() {
put();
}
public void put() {
System.out.println("welcome!!!");
}
}
//浏览器输入地址 http://localhost/login 回车就可以访问
@WebServlet("/login")//访问网站登录页
public class LoginServlet {
public void service() {
put();
}
public void put() {
System.out.println("用户登录成功!!!");
}
}