HTTP 协议构建于 TCP/IP 协议之上,是一个应用层协议。
维基百科:https://zh.wikipedia.org/wiki/%E8%B6%85%E6%96%87%E6%9C%AC%E4%BC%A0%E8%BE%93%E5%8D%8F%E8%AE%AE
HTTP 协议头信息必须是 ASCII 码,后面的数据可以是任何格式(服务器回应的时候,必须告诉客户端,数据是什么格式,Content-Type 字段的作用)。
https://developer.mozilla.org/zh-CN/docs/Web/HTTP
规范把 HTTP 请求分为三个部分:请求行(Request Line)、请求头(Headers)、消息主体(Body)。
其中,headers 和 body 是可选的。
Request Line 和 Headers 以 分隔,Headers 与 Body 之间有一个空行(两个 )。
<method> <request-URL> <version> <headers> <body>
method 有很多多,常见的就是 GET 和 POST。http 1.1 为了表达不同的语义,引入了诸如 DELETE、PATCH 等方法,这种语义不是强制性的。
协议并没有规定 GET 请求不能带 body 。理论上,任何 HTTP 请求都可以带 body 的。代理一般不会缓存 POST 请求的响应内容。
HTTP 请求例子:
GET / HTTP/1.1 Host: www.baidu.com Connection: keep-alive User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) Accept: */*
服务器响应
HTTP/1.1 200 OK Content-Type: text/plain Content-Length: 137582 Expires: Thu, 05 Dec 1997 16:00:00 GMT Last-Modified: Wed, 5 August 1996 15:55:28 GMT Server: Apache 0.84 <html> <body>Hello World</body> </html>
GET 产生一个 TCP 数据包,POST 产生两个TCP 数据包。
对于 GET 方式的请求,浏览器会把 http header 和 data 一并发送出去,服务器响应200(返回数据)。
对于 POST 方式的请求,浏览器先发送 header,服务器响应 100(continue),然后再发送 data,服务器响应200(返回数据)。
Java 实现的简单 HTTP 服务
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.*; import java.nio.charset.Charset; import java.util.Iterator; public class MyHttpServer { // 事件轮询器 private static Selector selector; // 服务通道 private static ServerSocketChannel serverSocketChannel; public static void main(String[] args) throws Exception { // 创建 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 监听 80 端口 serverSocketChannel.socket().bind(new InetSocketAddress(80)); // 设置为非阻塞模式 serverSocketChannel.configureBlocking(false); // 为 ssc 注册选择器 selector = Selector.open(); /** * 将通道注册到选择器上, 并且指定“监听接收事件” * SelectionKey.OP_ACCEPT —— 接收连接继续事件,表示服务器监听到了客户连接,服务器可以接收这个连接了 * SelectionKey.OP_CONNECT —— 连接就绪事件,表示客户与服务器的连接已经建立成功 * SelectionKey.OP_READ —— 读就绪事件,表示通道中已经有了可读的数据,可以执行读操作了(通道目前有数据,可以进行读操作了) * SelectionKey.OP_WRITE —— 写就绪事件,表示已经可以向通道写数据了(通道目前可以用于写操作) */ serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); // 创建处理器 while (true) { // 等待请求,每次等待阻塞 3s,超过 3s 后线程继续向下运行,如果传入 0 或者不传参数将一直阻塞 if (selector.select(3000) == 0) { continue; } // 获取待处理的SelectionKey Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator(); while (keyIter.hasNext()) { SelectionKey key = keyIter.next(); // 从待处理的 SelectionKey 迭代器中移除当前所使用的 key keyIter.remove(); // 启动新线程处理 SelectionKey new Thread(new HttpHandler(key)).run(); } } } private static class HttpHandler implements Runnable { private int bufferSize = 1024; private String localCharset = "UTF-8"; private SelectionKey key; public HttpHandler(SelectionKey key) { this.key = key; } public void handleAccept() throws IOException { serverSocketChannel = (ServerSocketChannel) key.channel(); // 建立和客户端的链接, 因为 OP_ACCEPT 是注册在 serverSocketChannel上,每个客户又有自己的 SocketChannel 通道 SocketChannel clientChannel = serverSocketChannel.accept(); clientChannel.configureBlocking(false); // 开启注册该客户端的可读时间,即该客户上传完所有的请求数据到系统 kernel 的 buffer 缓存中后,开启可读事件通知 clientChannel.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufferSize)); } public void handleRead() throws IOException { // 获取 channel SocketChannel sc = (SocketChannel) key.channel(); sc.configureBlocking(false); // 获取 buffer 并重置 ByteBuffer buffer = (ByteBuffer) key.attachment(); buffer.clear(); // 没有读到内容则关闭 if (sc.read(buffer) == -1) { sc.close(); } else { // 接收请求数据 buffer.flip(); String receivedString = Charset.forName(localCharset).newDecoder().decode(buffer).toString(); // 控制台打印请求报文头 String[] requestMessage = receivedString.split(" "); for (String s : requestMessage) { System.out.println(s); // 遇到空行说明报文头已经打印完 if (s.isEmpty()) { break; } } // 根据上面的处理情况,再注册这里的信息 sc.register(key.selector(), SelectionKey.OP_WRITE, requestMessage); } } public void handleWrite() throws IOException { SocketChannel sc = (SocketChannel) key.channel(); sc.configureBlocking(false); String[] requestMessage = (String[]) key.attachment(); // 返回客户端 StringBuilder sendString = new StringBuilder(); sendString.append("HTTP/1.1 200 OK "); // 响应报文首行,200 表示处理成功 sendString.append("Content-Type:text/html;charset=" + localCharset + " "); sendString.append(" "); // 报文头结束后加一个空行 sendString.append("<html><head><title>显示报文</title></head><body>"); sendString.append("接收到请求报文是:<br/>"); for (String s : requestMessage) { sendString.append(s + "<br/>"); } sendString.append("</body></html>"); ByteBuffer buffer = ByteBuffer.wrap(sendString.toString().getBytes(localCharset)); sc.write(buffer); sc.close(); // 根据上面的处理情况,再注册这里的信息 // sc.register(key.selector(), SelectionKey.OP_CONNECT); } @Override public void run() { try { if (key.isAcceptable()) { // 接收到连接请求时 handleAccept(); } else if (key.isReadable()) { // 读数据 handleRead(); } else if (key.isWritable()) { // 写数据 handleWrite(); } else if (key.isConnectable()) { System.out.println("isConnectable============"); } else if (key.isValid()) { System.out.println("isValid=================="); } } catch (IOException ex) { ex.printStackTrace(); } } } }
https://www.ruanyifeng.com/blog/2016/08/http.html
https://hit-alibaba.github.io/interview/basic/network/HTTP.html