时间:2023-05-24 19:06:01 | 来源:网站运营
时间:2023-05-24 19:06:01 来源:网站运营
简单实现一个 Java Web 服务器框架: GET /index/test HTTP/1.1 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-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9,sq;q=0.8 Connection: keep-alive Host: localhost User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36
第一行是请求行,分别用空格分割了三个部分:method、url、protocol HTTP/1.1 200 Content-Type: text/html <h1> Hello, web Framework! </h1>
第一行是响应行,分别用空格分割了两个部分:protocol 和 status ServerSocket serverSocket = new ServerSocket(80);
然后呢,Web 服务器在入口处从不拒绝任何连接,我们要接收所有的 socket 请求:accept
方法将阻塞当前程序,直到下一个 socket 连接请求到来,然后建立一个长连接。 while (true) { Socket client = serverSocket.accept(); }
当我们成功建立一个 socket 连接,此时我们先不关注请求报文,而是直接对它发送一个 http 响应报文:write
向对方写一些数据,flush
将强行写出所有数据,最后调用 close
关闭这个链接(http 是无状态的,如果不主动关闭该连接,客户端将认为此次请求并未结束)。 while (true) { Socket client = serverSocket.accept(); OutputStream clientOutStream = client.getOutputStream(); clientOutStream.write( ("HTTP/1.1 200/n" + "Content-Type: text/html/n" + "/n" + "<h1> Hello, web Framework! </h1>").getBytes() ); clientOutStream.flush(); clientOutStream.close(); }
import java.io.IOException; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; public class Main { public static void main(String[] args) { try { ServerSocket serverSocket = new ServerSocket(80); while (true) { Socket client = serverSocket.accept(); OutputStream clientOutStream = client.getOutputStream(); clientOutStream.write( ("HTTP/1.1 200/n" + "Content-Type: text/html/n" + "/n" + "<h1> Hello, web Framework! </h1>").getBytes() ); clientOutStream.flush(); clientOutStream.close(); } } catch (IOException e) { e.printStackTrace(); } } }
现在运行这个 Java 程序,然后用浏览器请求本地的 80 端口,你将看到: Angie app = new Angie(); app.use("/test", (req, res) -> { res.setStatus(200) .setHeaders("Content-Type", "text/html") .send("<h1> Hello, web framework! </h1>"); }); app.listen(80);
仓库 --> https://github.com/Drincann/Angie-javaAngie
,该类实例化的对象将作为一个独立的 web 服务。use
方法将一个回调方法注册到一个路由上,listen
方法用来开始监端口。listen
方法,listen
用来不断建立 socket 连接,并向客户端(浏览器)发送数据: public class Angie { public void listen(int port) { try { // 监听 port 端口 ServerSocket serverSocket = new ServerSocket(port); while (true) { // 接受连接 Socket client = serverSocket.accept(); // 创建新线程,发送数据 new Thread(() -> { try { OutputStream clientOutStream = client.getOutputStream(); clientOutStream.write( ("HTTP/1.1 200/n" + "Content-Type: text/html/n" + "/n" + "<h1> Hello, web Framework! </h1>").getBytes() ); clientOutStream.flush(); clientOutStream.close(); } catch (IOException e) { e.printStackTrace(); } }).start(); } } catch (IOException e) { e.printStackTrace(); } } }
注意这里对每个 socket 请求都创建了一个新线程处理,用来提高并发性能。这时候我们就可以这样写:
Angie app = new Angie(); app.listen(80);
use
,注册一个回调方法到某个路由: private final HashMap<String, Processor> routeMap = new HashMap();
然后在开发者调用 use
时记录这个映射关系: public void use(String route, Processor processor) { routeMap.put(route, processor); }
Processor
是回调方法的函数式类型,正常来讲,应该接收一个 request 参数和一个 response 参数。listen
中的 while,不要再回复固定内容了,而是向不同路由的回调方法分发消息: while (true) { // 接受连接 Socket client = serverSocket.accept(); // 创建新线程,发送数据 new Thread(() -> { try { // 分发消息 Request request = new Request(client.getInputStream()); Response response = new Response(client.getOutputStream()); if (routeMap.containsKey(request.getUrl())) { routeMap.get(request.getUrl()).callback(request, response); } else { response.setStatus(404).send(request.getUrl() + " not found"); } } catch (IOException e) { e.printStackTrace(); } }).start(); }
到现在为止,入口类 Angie
的代码就已经全部完成了: package cool.gaolihai; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.HashMap; public class Angie { private final HashMap<String, Processor> routeMap = new HashMap(); public void use(String route, Processor processor) { routeMap.put(route, processor); } public void listen(int port) { try { ServerSocket serverSocket = new ServerSocket(port); while (true) { Socket client = serverSocket.accept(); new Thread(() -> { try { Request request = new Request(client.getInputStream()); Response response = new Response(client.getOutputStream()); if (routeMap.containsKey(request.getUrl())) { routeMap.get(request.getUrl()).callback(request, response); } else { response.setStatus(404).send(request.getUrl() + " not found"); } } catch (IOException e) { e.printStackTrace(); } }).start(); } } catch (IOException e) { e.printStackTrace(); } } }
Request
、Response
就是刚才 Processor
需要接收的参数,下面我们来实现这些类型。Processor
非常简单,他就是一个函数式接口: package cool.gaolihai; @FunctionalInterface public interface Processor { void callback(Request request, Response response); }
Request
负责解析请求报文,在这里,我们仅解析 url、GET 请求的 params 和 method,我们创建对应的属性和访问器: private String url; private String params; private String method; public String getUrl() { return url; } public String getParams() { return params; } public String getMethod() { return method; }
可以看到,在入口类中,我们将 socket 的输入流作为构造参数进行实例化,所以构造函数如下:public Request(InputStream inputStream){ try { String[] requestLine = new BufferedReader(new InputStreamReader(inputStream)).readLine().split(" "); if (requestLine.length == 3 && requestLine[2].equals("HTTP/1.1")) { this.method = requestLine[0]; String fullUrl = requestLine[1]; if (fullUrl.contains("?")) { this.url = fullUrl.substring(0, fullUrl.indexOf("?")); this.params = fullUrl.substring(fullUrl.indexOf("?") + 1); } else { this.url = fullUrl; } } } catch (IOException e) { e.printStackTrace(); }}
Request
这就完成了,完整代码如下: package cool.gaolihai; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; public class Request { private String url; private String params; private String method; public Request(InputStream inputStream){ try { String[] requestLine = new BufferedReader(new InputStreamReader(inputStream)).readLine().split(" "); if (requestLine.length == 3 && requestLine[2].equals("HTTP/1.1")) { this.method = requestLine[0]; String fullUrl = requestLine[1]; if (fullUrl.contains("?")) { this.url = fullUrl.substring(0, fullUrl.indexOf("?")); this.params = fullUrl.substring(fullUrl.indexOf("?") + 1); } else { this.url = fullUrl; } } } catch (IOException e) { e.printStackTrace(); } } public String getUrl() { return url; } public String getParams() { return params; } public String getMethod() { return method; } }
Response
类中,我们需要给开发者提供设置请求头、设置响应状态吗和响应数据的能力: private HashMap<String, String> headers = new HashMap<>(); public Response setHeaders(String key, String value) { this.headers.put(key, value); return this; }
对于响应状态码,我们这样实现: private int status; public Response setStatus(int statusCode) { this.status = statusCode; return this; }
在这里,我们还要将输出流放在内部维护: private OutputStream outputStream;
构造函数则是直接初始化输出流:public Response(OutputStream outputStream) { this.outputStream = outputStream;}
最后一个功能,发送数据,实际上是向输出流中写字符串:public void send(String data) { try { StringBuilder dataBuilder = new StringBuilder(); // 拼接请求行 dataBuilder.append("HTTP/1.1 ").append(this.status).append("/n"); // 拼接请求头 for (String key: this.headers.keySet()) { dataBuilder.append(key).append(": ").append(this.headers.get(key)).append("/n"); } // 拼接请求体 dataBuilder.append("/n").append(data); outputStream.write(dataBuilder.toString().getBytes()); outputStream.flush(); outputStream.close(); } catch (IOException e) { e.printStackTrace(); }}
到这里 Response
也实现完毕了,代码如下: package cool.gaolihai; import java.io.IOException; import java.io.OutputStream; import java.util.HashMap; public class Response { private OutputStream outputStream; private HashMap<String, String> headers = new HashMap<>(); private int status; public Response(OutputStream outputStream) { this.outputStream = outputStream; } public Response setHeaders(String key, String value) { this.headers.put(key, value); return this; } public Response setStatus(int statusCode) { this.status = statusCode; return this; } public void send(String data) { try { StringBuilder dataBuilder = new StringBuilder(); dataBuilder.append("HTTP/1.1 ").append(this.status).append("/n"); for (String key: this.headers.keySet()) { dataBuilder.append(key).append(": ").append(this.headers.get(key)).append("/n"); } dataBuilder.append("/n").append(data); outputStream.write(dataBuilder.toString().getBytes()); outputStream.flush(); outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } }
Angie
,其每一个实例都是一个独立的 Web 服务端。Processor
,用来通过 use
方法接入框架的处理流程,还有与之相关的 Request
和 Response
类型,用来解析请求报文和提供开发者响应客户端的能力。 import cool.gaolihai.Angie; public class app { public static void main(String[] args) { Angie app = new Angie(); app.use("/test", (req, res) -> { res.setStatus(200) .setHeaders("Content-Type", "text/html") .send("<h1> Hello, web framework! </h1>"); }); app.listen(80); } }
由于笔者没有基本的 Java 功底,对一些机制并不了解,目前这个框架偶然会玄学地在线程中抛出 NullPointerException
异常,在异常的栈轨迹中也没有触发任何断点。关键词:服务,实现,简单